// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tls

import (
	
	
	

	
)

type echCipher struct {
	KDFID  uint16
	AEADID uint16
}

type echExtension struct {
	Type uint16
	Data []byte
}

type echConfig struct {
	raw []byte

	Version uint16
	Length  uint16

	ConfigID             uint8
	KemID                uint16
	PublicKey            []byte
	SymmetricCipherSuite []echCipher

	MaxNameLength uint8
	PublicName    []byte
	Extensions    []echExtension
}

var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList")

// parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
// slice of parsed ECHConfigs, in the same order they were parsed, or an error
// if the list is malformed.
func parseECHConfigList( []byte) ([]echConfig, error) {
	 := cryptobyte.String()
	// Skip the length prefix
	var  uint16
	if !.ReadUint16(&) {
		return nil, errMalformedECHConfig
	}
	if  != uint16(len()-2) {
		return nil, errMalformedECHConfig
	}
	var  []echConfig
	for len() > 0 {
		var  echConfig
		.raw = []byte()
		if !.ReadUint16(&.Version) {
			return nil, errMalformedECHConfig
		}
		if !.ReadUint16(&.Length) {
			return nil, errMalformedECHConfig
		}
		if len(.raw) < int(.Length)+4 {
			return nil, errMalformedECHConfig
		}
		.raw = .raw[:.Length+4]
		if .Version != extensionEncryptedClientHello {
			.Skip(int(.Length))
			continue
		}
		if !.ReadUint8(&.ConfigID) {
			return nil, errMalformedECHConfig
		}
		if !.ReadUint16(&.KemID) {
			return nil, errMalformedECHConfig
		}
		if !.ReadUint16LengthPrefixed((*cryptobyte.String)(&.PublicKey)) {
			return nil, errMalformedECHConfig
		}
		var  cryptobyte.String
		if !.ReadUint16LengthPrefixed(&) {
			return nil, errMalformedECHConfig
		}
		for !.Empty() {
			var  echCipher
			if !.ReadUint16(&.KDFID) {
				return nil, errMalformedECHConfig
			}
			if !.ReadUint16(&.AEADID) {
				return nil, errMalformedECHConfig
			}
			.SymmetricCipherSuite = append(.SymmetricCipherSuite, )
		}
		if !.ReadUint8(&.MaxNameLength) {
			return nil, errMalformedECHConfig
		}
		var  cryptobyte.String
		if !.ReadUint8LengthPrefixed(&) {
			return nil, errMalformedECHConfig
		}
		.PublicName = 
		var  cryptobyte.String
		if !.ReadUint16LengthPrefixed(&) {
			return nil, errMalformedECHConfig
		}
		for !.Empty() {
			var  echExtension
			if !.ReadUint16(&.Type) {
				return nil, errMalformedECHConfig
			}
			if !.ReadUint16LengthPrefixed((*cryptobyte.String)(&.Data)) {
				return nil, errMalformedECHConfig
			}
			.Extensions = append(.Extensions, )
		}

		 = append(, )
	}
	return , nil
}

func pickECHConfig( []echConfig) *echConfig {
	for ,  := range  {
		if ,  := hpke.SupportedKEMs[.KemID]; ! {
			continue
		}
		var  bool
		for ,  := range .SymmetricCipherSuite {
			if ,  := hpke.SupportedAEADs[.AEADID]; ! {
				continue
			}
			if ,  := hpke.SupportedKDFs[.KDFID]; ! {
				continue
			}
			 = true
			break
		}
		if ! {
			continue
		}
		if !validDNSName(string(.PublicName)) {
			continue
		}
		var  bool
		for ,  := range .Extensions {
			// If high order bit is set to 1 the extension is mandatory.
			// Since we don't support any extensions, if we see a mandatory
			// bit, we skip the config.
			if .Type&uint16(1<<15) != 0 {
				 = true
			}
		}
		if  {
			continue
		}
		return &
	}
	return nil
}

func pickECHCipherSuite( []echCipher) (echCipher, error) {
	for ,  := range  {
		// NOTE: all of the supported AEADs and KDFs are fine, rather than
		// imposing some sort of preference here, we just pick the first valid
		// suite.
		if ,  := hpke.SupportedAEADs[.AEADID]; ! {
			continue
		}
		if ,  := hpke.SupportedKDFs[.KDFID]; ! {
			continue
		}
		return , nil
	}
	return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
}

func encodeInnerClientHello( *clientHelloMsg,  int) ([]byte, error) {
	,  := .marshalMsg(true)
	if  != nil {
		return nil, 
	}
	 = [4:] // strip four byte prefix

	var  int
	if .serverName != "" {
		 = max(0, -len(.serverName))
	} else {
		 =  + 9
	}
	 = 31 - ((len() +  - 1) % 32)

	return append(, make([]byte, )...), nil
}

func generateOuterECHExt( uint8, ,  uint16,  []byte,  []byte) ([]byte, error) {
	var  cryptobyte.Builder
	.AddUint8(0) // outer
	.AddUint16()
	.AddUint16()
	.AddUint8()
	.AddUint16LengthPrefixed(func( *cryptobyte.Builder) { .AddBytes() })
	.AddUint16LengthPrefixed(func( *cryptobyte.Builder) { .AddBytes() })
	return .Bytes()
}

func computeAndUpdateOuterECHExtension(,  *clientHelloMsg,  *echContext,  bool) error {
	var  []byte
	if  {
		 = .encapsulatedKey
	}
	,  := encodeInnerClientHello(, int(.config.MaxNameLength))
	if  != nil {
		return 
	}
	// NOTE: the tag lengths for all of the supported AEADs are the same (16
	// bytes), so we have hardcoded it here. If we add support for another AEAD
	// with a different tag length, we will need to change this.
	 := len() + 16 // AEAD tag length
	.encryptedClientHello,  = generateOuterECHExt(.config.ConfigID, .kdfID, .aeadID, , make([]byte, ))
	if  != nil {
		return 
	}
	,  := .marshal()
	if  != nil {
		return 
	}
	 = [4:] // strip the four byte prefix
	,  := .hpkeContext.Seal(, )
	if  != nil {
		return 
	}
	.encryptedClientHello,  = generateOuterECHExt(.config.ConfigID, .kdfID, .aeadID, , )
	if  != nil {
		return 
	}
	return nil
}

// validDNSName is a rather rudimentary check for the validity of a DNS name.
// This is used to check if the public_name in a ECHConfig is valid when we are
// picking a config. This can be somewhat lax because even if we pick a
// valid-looking name, the DNS layer will later reject it anyway.
func validDNSName( string) bool {
	if len() > 253 {
		return false
	}
	 := strings.Split(, ".")
	if len() <= 1 {
		return false
	}
	for ,  := range  {
		 := len()
		if  == 0 {
			return false
		}
		for ,  := range  {
			if  == '-' && ( == 0 ||  == -1) {
				return false
			}
			if ( < '0' ||  > '9') && ( < 'a' ||  > 'z') && ( < 'A' ||  > 'Z') &&  != '-' {
				return false
			}
		}
	}
	return true
}

// ECHRejectionError is the error type returned when ECH is rejected by a remote
// server. If the server offered a ECHConfigList to use for retries, the
// RetryConfigList field will contain this list.
//
// The client may treat an ECHRejectionError with an empty set of RetryConfigs
// as a secure signal from the server.
type ECHRejectionError struct {
	RetryConfigList []byte
}

func ( *ECHRejectionError) () string {
	return "tls: server rejected ECH"
}