package quic

import (
	
	
	
	
	
	

	
	
	
)

const disableClientHelloScramblingEnv = "QUIC_GO_DISABLE_CLIENTHELLO_SCRAMBLING"

// The baseCryptoStream is used by the cryptoStream and the initialCryptoStream.
// This allows us to implement different logic for PopCryptoFrame for the two streams.
type baseCryptoStream struct {
	queue frameSorter

	highestOffset protocol.ByteCount
	finished      bool

	writeOffset protocol.ByteCount
	writeBuf    []byte
}

func newCryptoStream() *cryptoStream {
	return &cryptoStream{baseCryptoStream{queue: *newFrameSorter()}}
}

func ( *baseCryptoStream) ( *wire.CryptoFrame) error {
	 := .Offset + protocol.ByteCount(len(.Data))
	if  := ;  > protocol.MaxCryptoStreamOffset {
		return &qerr.TransportError{
			ErrorCode:    qerr.CryptoBufferExceeded,
			ErrorMessage: fmt.Sprintf("received invalid offset %d on crypto stream, maximum allowed %d", , protocol.MaxCryptoStreamOffset),
		}
	}
	if .finished {
		if  > .highestOffset {
			// reject crypto data received after this stream was already finished
			return &qerr.TransportError{
				ErrorCode:    qerr.ProtocolViolation,
				ErrorMessage: "received crypto data after change of encryption level",
			}
		}
		// ignore data with a smaller offset than the highest received
		// could e.g. be a retransmission
		return nil
	}
	.highestOffset = max(.highestOffset, )
	return .queue.Push(.Data, .Offset, nil)
}

// GetCryptoData retrieves data that was received in CRYPTO frames
func ( *baseCryptoStream) () []byte {
	, ,  := .queue.Pop()
	return 
}

func ( *baseCryptoStream) () error {
	if .queue.HasMoreData() {
		return &qerr.TransportError{
			ErrorCode:    qerr.ProtocolViolation,
			ErrorMessage: "encryption level changed, but crypto stream has more data to read",
		}
	}
	.finished = true
	return nil
}

// Writes writes data that should be sent out in CRYPTO frames
func ( *baseCryptoStream) ( []byte) (int, error) {
	.writeBuf = append(.writeBuf, ...)
	return len(), nil
}

func ( *baseCryptoStream) () bool {
	return len(.writeBuf) > 0
}

func ( *baseCryptoStream) ( protocol.ByteCount) *wire.CryptoFrame {
	 := &wire.CryptoFrame{Offset: .writeOffset}
	 := min(.MaxDataLen(), protocol.ByteCount(len(.writeBuf)))
	if  <= 0 {
		return nil
	}
	.Data = .writeBuf[:]
	.writeBuf = .writeBuf[:]
	.writeOffset += 
	return 
}

type cryptoStream struct {
	baseCryptoStream
}

type clientHelloCut struct {
	start protocol.ByteCount
	end   protocol.ByteCount
}

type initialCryptoStream struct {
	baseCryptoStream

	scramble bool
	end      protocol.ByteCount
	cuts     [2]clientHelloCut
}

func newInitialCryptoStream( bool) *initialCryptoStream {
	var  bool
	if  {
		,  := strconv.ParseBool(os.Getenv(disableClientHelloScramblingEnv))
		 =  != nil || !
	}
	 := &initialCryptoStream{
		baseCryptoStream: baseCryptoStream{queue: *newFrameSorter()},
		scramble:         ,
	}
	for  := range len(.cuts) {
		.cuts[].start = protocol.InvalidByteCount
		.cuts[].end = protocol.InvalidByteCount
	}
	return 
}

func ( *initialCryptoStream) () bool {
	// The ClientHello might be written in multiple parts.
	// In order to correctly split the ClientHello, we need the entire ClientHello has been queued.
	if .scramble && .writeOffset == 0 && .cuts[0].start == protocol.InvalidByteCount {
		return false
	}
	return .baseCryptoStream.HasData()
}

func ( *initialCryptoStream) ( []byte) (int, error) {
	.writeBuf = append(.writeBuf, ...)
	if !.scramble {
		return len(), nil
	}
	if .cuts[0].start == protocol.InvalidByteCount {
		, , ,  := findSNIAndECH(.writeBuf)
		if errors.Is(, io.ErrUnexpectedEOF) {
			return len(), nil
		}
		if  != nil {
			return len(), 
		}
		if  == -1 &&  == -1 {
			// Neither SNI nor ECH found.
			// There's nothing to scramble.
			.scramble = false
			return len(), nil
		}
		.end = protocol.ByteCount(len(.writeBuf))
		.cuts[0].start = protocol.ByteCount( + /2) // right in the middle
		.cuts[0].end = protocol.ByteCount( + )
		if  > 0 {
			// ECH extension found, cut the ECH extension type value (a uint16) in half
			 := protocol.ByteCount( + 1)
			.cuts[1].start = 
			// cut somewhere (16 bytes), most likely in the ECH extension value
			.cuts[1].end = min(+16, .end)
		}
		slices.SortFunc(.cuts[:], func(,  clientHelloCut) int {
			if .start == protocol.InvalidByteCount {
				return 1
			}
			if .start > .start {
				return 1
			}
			return -1
		})
	}
	return len(), nil
}

func ( *initialCryptoStream) ( protocol.ByteCount) *wire.CryptoFrame {
	if !.scramble {
		return .baseCryptoStream.PopCryptoFrame()
	}

	// send out the skipped parts
	if .writeOffset == .end {
		var  bool
		var  *wire.CryptoFrame
		for ,  := range .cuts {
			if .start == protocol.InvalidByteCount {
				continue
			}
			 = true
			if  != nil {
				break
			}
			 = &wire.CryptoFrame{Offset: .start}
			 := min(.MaxDataLen(), .end-.start)
			if  <= 0 {
				return nil
			}
			.Data = .writeBuf[.start : .start+]
			.cuts[].start += 
			if .cuts[].start == .end {
				.cuts[].start = protocol.InvalidByteCount
				.cuts[].end = protocol.InvalidByteCount
				 = false
			}
		}
		if ! {
			// no more cuts found, we're done sending out everything up until s.end
			.writeBuf = .writeBuf[.end:]
			.end = protocol.InvalidByteCount
			.scramble = false
		}
		return 
	}

	 := clientHelloCut{start: protocol.InvalidByteCount, end: protocol.InvalidByteCount}
	for ,  := range .cuts {
		if .start == protocol.InvalidByteCount {
			continue
		}
		if .start > .writeOffset {
			 = 
			break
		}
	}
	 := &wire.CryptoFrame{Offset: .writeOffset}
	 := .start
	if  == protocol.InvalidByteCount {
		 = .end
	}
	 := min(.MaxDataLen(), -.writeOffset)
	if  <= 0 {
		return nil
	}
	.Data = .writeBuf[.writeOffset : .writeOffset+]
	// Don't reslice the writeBuf yet.
	// This is done once all parts have been sent out.
	.writeOffset += 
	if .writeOffset == .start {
		.writeOffset = .end
	}

	return 
}