package handshake

import (
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
)

type quicVersionContextKey struct{}

var QUICVersionContextKey = &quicVersionContextKey{}

const clientSessionStateRevision = 5

type cryptoSetup struct {
	tlsConf *tls.Config
	conn    *tls.QUICConn

	events []Event

	version protocol.Version

	ourParams  *wire.TransportParameters
	peerParams *wire.TransportParameters

	zeroRTTParameters *wire.TransportParameters
	allow0RTT         bool

	rttStats *utils.RTTStats

	qlogger qlogwriter.Recorder
	logger  utils.Logger

	perspective protocol.Perspective

	handshakeCompleteTime time.Time

	zeroRTTOpener LongHeaderOpener // only set for the server
	zeroRTTSealer LongHeaderSealer // only set for the client

	initialOpener LongHeaderOpener
	initialSealer LongHeaderSealer

	handshakeOpener LongHeaderOpener
	handshakeSealer LongHeaderSealer

	used0RTT atomic.Bool

	aead          *updatableAEAD
	has1RTTSealer bool
	has1RTTOpener bool
}

var _ CryptoSetup = &cryptoSetup{}

// NewCryptoSetupClient creates a new crypto setup for the client
func (
	 protocol.ConnectionID,
	 *wire.TransportParameters,
	 *tls.Config,
	 bool,
	 *utils.RTTStats,
	 qlogwriter.Recorder,
	 utils.Logger,
	 protocol.Version,
) CryptoSetup {
	 := newCryptoSetup(
		,
		,
		,
		,
		,
		protocol.PerspectiveClient,
		,
	)

	 = .Clone()
	.MinVersion = tls.VersionTLS13
	.tlsConf = 
	.allow0RTT = 

	.conn = tls.QUICClient(&tls.QUICConfig{
		TLSConfig:           ,
		EnableSessionEvents: true,
	})
	.conn.SetTransportParameters(.ourParams.Marshal(protocol.PerspectiveClient))

	return 
}

// NewCryptoSetupServer creates a new crypto setup for the server
func (
	 protocol.ConnectionID,
	,  net.Addr,
	 *wire.TransportParameters,
	 *tls.Config,
	 bool,
	 *utils.RTTStats,
	 qlogwriter.Recorder,
	 utils.Logger,
	 protocol.Version,
) CryptoSetup {
	 := newCryptoSetup(
		,
		,
		,
		,
		,
		protocol.PerspectiveServer,
		,
	)
	.allow0RTT = 

	 = setupConfigForServer(, , )

	.tlsConf = 
	.conn = tls.QUICServer(&tls.QUICConfig{
		TLSConfig:           ,
		EnableSessionEvents: true,
	})
	return 
}

func newCryptoSetup(
	 protocol.ConnectionID,
	 *wire.TransportParameters,
	 *utils.RTTStats,
	 qlogwriter.Recorder,
	 utils.Logger,
	 protocol.Perspective,
	 protocol.Version,
) *cryptoSetup {
	,  := NewInitialAEAD(, , )
	if  != nil {
		.RecordEvent(qlog.KeyUpdated{
			Trigger: qlog.KeyUpdateTLS,
			KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveClient),
		})
		.RecordEvent(qlog.KeyUpdated{
			Trigger: qlog.KeyUpdateTLS,
			KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveServer),
		})
	}
	return &cryptoSetup{
		initialSealer: ,
		initialOpener: ,
		aead:          newUpdatableAEAD(, , , ),
		events:        make([]Event, 0, 16),
		ourParams:     ,
		rttStats:      ,
		qlogger:       ,
		logger:        ,
		perspective:   ,
		version:       ,
	}
}

func ( *cryptoSetup) ( protocol.ConnectionID) {
	,  := NewInitialAEAD(, .perspective, .version)
	.initialSealer = 
	.initialOpener = 
	if .qlogger != nil {
		.qlogger.RecordEvent(qlog.KeyUpdated{
			Trigger: qlog.KeyUpdateTLS,
			KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveClient),
		})
		.qlogger.RecordEvent(qlog.KeyUpdated{
			Trigger: qlog.KeyUpdateTLS,
			KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveServer),
		})
	}
}

func ( *cryptoSetup) ( protocol.PacketNumber) error {
	return .aead.SetLargestAcked()
}

func ( *cryptoSetup) ( context.Context) error {
	 := .conn.Start(context.WithValue(, QUICVersionContextKey, .version))
	if  != nil {
		return wrapError()
	}
	for {
		 := .conn.NextEvent()
		if  := .handleEvent();  != nil {
			return wrapError()
		}
		if .Kind == tls.QUICNoEvent {
			break
		}
	}
	if .perspective == protocol.PerspectiveClient {
		if .zeroRTTSealer != nil && .zeroRTTParameters != nil {
			.logger.Debugf("Doing 0-RTT.")
			.events = append(.events, Event{Kind: EventRestoredTransportParameters, TransportParameters: .zeroRTTParameters})
		} else {
			.logger.Debugf("Not doing 0-RTT. Has sealer: %t, has params: %t", .zeroRTTSealer != nil, .zeroRTTParameters != nil)
		}
	}
	return nil
}

// Close closes the crypto setup.
// It aborts the handshake, if it is still running.
func ( *cryptoSetup) () error {
	return .conn.Close()
}

// HandleMessage handles a TLS handshake message.
// It is called by the crypto streams when a new message is available.
func ( *cryptoSetup) ( []byte,  protocol.EncryptionLevel) error {
	if  := .handleMessage(, );  != nil {
		return wrapError()
	}
	return nil
}

func ( *cryptoSetup) ( []byte,  protocol.EncryptionLevel) error {
	if  := .conn.HandleData(.ToTLSEncryptionLevel(), );  != nil {
		return 
	}
	for {
		 := .conn.NextEvent()
		if  := .handleEvent();  != nil {
			return 
		}
		if .Kind == tls.QUICNoEvent {
			return nil
		}
	}
}

func ( *cryptoSetup) ( tls.QUICEvent) ( error) {
	switch .Kind {
	case tls.QUICNoEvent:
		return nil
	case tls.QUICSetReadSecret:
		.setReadKey(.Level, .Suite, .Data)
		return nil
	case tls.QUICSetWriteSecret:
		.setWriteKey(.Level, .Suite, .Data)
		return nil
	case tls.QUICTransportParameters:
		return .handleTransportParameters(.Data)
	case tls.QUICTransportParametersRequired:
		.conn.SetTransportParameters(.ourParams.Marshal(.perspective))
		return nil
	case tls.QUICRejectedEarlyData:
		.rejected0RTT()
		return nil
	case tls.QUICWriteData:
		.writeRecord(.Level, .Data)
		return nil
	case tls.QUICHandshakeDone:
		.handshakeComplete()
		return nil
	case tls.QUICStoreSession:
		if .perspective == protocol.PerspectiveServer {
			panic("cryptoSetup BUG: unexpected QUICStoreSession event for the server")
		}
		.SessionState.Extra = append(
			.SessionState.Extra,
			addSessionStateExtraPrefix(.marshalDataForSessionState(.SessionState.EarlyData)),
		)
		return .conn.StoreSession(.SessionState)
	case tls.QUICResumeSession:
		var  bool
		switch .perspective {
		case protocol.PerspectiveClient:
			// for clients, this event occurs when a session ticket is selected
			 = .handleDataFromSessionState(
				findSessionStateExtraData(.SessionState.Extra),
				.SessionState.EarlyData,
			)
		case protocol.PerspectiveServer:
			// for servers, this event occurs when receiving the client's session ticket
			 = .handleSessionTicket(
				findSessionStateExtraData(.SessionState.Extra),
				.SessionState.EarlyData,
			)
		}
		if .SessionState.EarlyData {
			.SessionState.EarlyData = 
		}
		return nil
	default:
		// Unknown events should be ignored.
		// crypto/tls will ensure that this is safe to do.
		// See the discussion following https://github.com/golang/go/issues/68124#issuecomment-2187042510 for details.
		return nil
	}
}

func ( *cryptoSetup) () Event {
	if len(.events) == 0 {
		return Event{Kind: EventNoEvent}
	}
	 := .events[0]
	.events = .events[1:]
	return 
}

func ( *cryptoSetup) ( []byte) error {
	var  wire.TransportParameters
	if  := .Unmarshal(, .perspective.Opposite());  != nil {
		return 
	}
	.peerParams = &
	.events = append(.events, Event{Kind: EventReceivedTransportParameters, TransportParameters: .peerParams})
	return nil
}

// must be called after receiving the transport parameters
func ( *cryptoSetup) ( bool) []byte {
	 := make([]byte, 0, 256)
	 = quicvarint.Append(, clientSessionStateRevision)
	if  {
		// only save the transport parameters for 0-RTT enabled session tickets
		return .peerParams.MarshalForSessionTicket()
	}
	return 
}

func ( *cryptoSetup) ( []byte,  bool) ( bool) {
	,  := decodeDataFromSessionState(, )
	if  != nil {
		.logger.Debugf("Restoring of transport parameters from session ticket failed: %s", .Error())
		return
	}
	// The session ticket might have been saved from a connection that allowed 0-RTT,
	// and therefore contain transport parameters.
	// Only use them if 0-RTT is actually used on the new connection.
	if  != nil && .allow0RTT {
		.zeroRTTParameters = 
		return true
	}
	return false
}

func decodeDataFromSessionState( []byte,  bool) (*wire.TransportParameters, error) {
	, ,  := quicvarint.Parse()
	if  != nil {
		return nil, 
	}
	 = [:]
	if  != clientSessionStateRevision {
		return nil, fmt.Errorf("mismatching version. Got %d, expected %d", , clientSessionStateRevision)
	}
	if ! {
		return nil, nil
	}
	var  wire.TransportParameters
	if  := .UnmarshalFromSessionTicket();  != nil {
		return nil, 
	}
	return &, nil
}

func ( *cryptoSetup) () []byte {
	return (&sessionTicket{
		Parameters: .ourParams,
	}).Marshal()
}

// GetSessionTicket generates a new session ticket.
// Due to limitations in crypto/tls, it's only possible to generate a single session ticket per connection.
// It is only valid for the server.
func ( *cryptoSetup) () ([]byte, error) {
	if  := .conn.SendSessionTicket(tls.QUICSessionTicketOptions{
		EarlyData: .allow0RTT,
		Extra:     [][]byte{addSessionStateExtraPrefix(.getDataForSessionTicket())},
	});  != nil {
		// Session tickets might be disabled by tls.Config.SessionTicketsDisabled.
		// We can't check h.tlsConfig here, since the actual config might have been obtained from
		// the GetConfigForClient callback.
		// See https://github.com/golang/go/issues/62032.
		// This error assertion can be removed once we drop support for Go 1.25.
		if strings.Contains(.Error(), "session ticket keys unavailable") {
			return nil, nil
		}
		return nil, 
	}
	// If session tickets are disabled, NextEvent will immediately return QUICNoEvent,
	// and we will return a nil ticket.
	var  []byte
	for {
		 := .conn.NextEvent()
		if .Kind == tls.QUICNoEvent {
			break
		}
		if .Kind == tls.QUICWriteData && .Level == tls.QUICEncryptionLevelApplication {
			if  != nil {
				.logger.Errorf("unexpected multiple session tickets")
				continue
			}
			 = .Data
		} else {
			.logger.Errorf("unexpected event: %v", .Kind)
		}
	}
	return , nil
}

// handleSessionTicket is called for the server when receiving the client's session ticket.
// It reads parameters from the session ticket and checks whether to accept 0-RTT if the session ticket enabled 0-RTT.
// Note that the fact that the session ticket allows 0-RTT doesn't mean that the actual TLS handshake enables 0-RTT:
// A client may use a 0-RTT enabled session to resume a TLS session without using 0-RTT.
func ( *cryptoSetup) ( []byte,  bool) ( bool) {
	var  sessionTicket
	if  := .Unmarshal();  != nil {
		.logger.Debugf("Unmarshalling session ticket failed: %s", .Error())
		return false
	}
	if ! {
		return false
	}
	 := .ourParams.ValidFor0RTT(.Parameters)
	if ! {
		.logger.Debugf("Transport parameters changed. Rejecting 0-RTT.")
		return false
	}
	if !.allow0RTT {
		.logger.Debugf("0-RTT not allowed. Rejecting 0-RTT.")
		return false
	}
	return true
}

// rejected0RTT is called for the client when the server rejects 0-RTT.
func ( *cryptoSetup) () {
	.logger.Debugf("0-RTT was rejected. Dropping 0-RTT keys.")

	 := .zeroRTTSealer != nil
	.zeroRTTSealer = nil

	if  {
		.events = append(.events, Event{Kind: EventDiscard0RTTKeys})
	}
}

func ( *cryptoSetup) ( tls.QUICEncryptionLevel,  uint16,  []byte) {
	 := getCipherSuite()
	//nolint:exhaustive // The TLS stack doesn't export Initial keys.
	switch  {
	case tls.QUICEncryptionLevelEarly:
		if .perspective == protocol.PerspectiveClient {
			panic("Received 0-RTT read key for the client")
		}
		.zeroRTTOpener = newLongHeaderOpener(
			createAEAD(, , .version),
			newHeaderProtector(, , true, .version),
		)
		.used0RTT.Store(true)
		if .logger.Debug() {
			.logger.Debugf("Installed 0-RTT Read keys (using %s)", tls.CipherSuiteName(.ID))
		}
	case tls.QUICEncryptionLevelHandshake:
		.handshakeOpener = newLongHeaderOpener(
			createAEAD(, , .version),
			newHeaderProtector(, , true, .version),
		)
		if .logger.Debug() {
			.logger.Debugf("Installed Handshake Read keys (using %s)", tls.CipherSuiteName(.ID))
		}
	case tls.QUICEncryptionLevelApplication:
		.aead.SetReadKey(, )
		.has1RTTOpener = true
		if .logger.Debug() {
			.logger.Debugf("Installed 1-RTT Read keys (using %s)", tls.CipherSuiteName(.ID))
		}
	default:
		panic("unexpected read encryption level")
	}
	.events = append(.events, Event{Kind: EventReceivedReadKeys})
	if .qlogger != nil {
		.qlogger.RecordEvent(qlog.KeyUpdated{
			Trigger: qlog.KeyUpdateTLS,
			KeyType: encLevelToKeyType(protocol.FromTLSEncryptionLevel(), .perspective.Opposite()),
		})
	}
}

func ( *cryptoSetup) ( tls.QUICEncryptionLevel,  uint16,  []byte) {
	 := getCipherSuite()
	//nolint:exhaustive // The TLS stack doesn't export Initial keys.
	switch  {
	case tls.QUICEncryptionLevelEarly:
		if .perspective == protocol.PerspectiveServer {
			panic("Received 0-RTT write key for the server")
		}
		.zeroRTTSealer = newLongHeaderSealer(
			createAEAD(, , .version),
			newHeaderProtector(, , true, .version),
		)
		if .logger.Debug() {
			.logger.Debugf("Installed 0-RTT Write keys (using %s)", tls.CipherSuiteName(.ID))
		}
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.KeyUpdated{
				Trigger: qlog.KeyUpdateTLS,
				KeyType: encLevelToKeyType(protocol.Encryption0RTT, .perspective),
			})
		}
		// don't set used0RTT here. 0-RTT might still get rejected.
		return
	case tls.QUICEncryptionLevelHandshake:
		.handshakeSealer = newLongHeaderSealer(
			createAEAD(, , .version),
			newHeaderProtector(, , true, .version),
		)
		if .logger.Debug() {
			.logger.Debugf("Installed Handshake Write keys (using %s)", tls.CipherSuiteName(.ID))
		}
	case tls.QUICEncryptionLevelApplication:
		.aead.SetWriteKey(, )
		.has1RTTSealer = true
		if .logger.Debug() {
			.logger.Debugf("Installed 1-RTT Write keys (using %s)", tls.CipherSuiteName(.ID))
		}
		if .zeroRTTSealer != nil {
			// Once we receive handshake keys, we know that 0-RTT was not rejected.
			.used0RTT.Store(true)
			.zeroRTTSealer = nil
			.logger.Debugf("Dropping 0-RTT keys.")
			if .qlogger != nil {
				.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClient0RTT})
			}
		}
	default:
		panic("unexpected write encryption level")
	}
	if .qlogger != nil {
		.qlogger.RecordEvent(qlog.KeyUpdated{
			Trigger: qlog.KeyUpdateTLS,
			KeyType: encLevelToKeyType(protocol.FromTLSEncryptionLevel(), .perspective),
		})
	}
}

// writeRecord is called when TLS writes data
func ( *cryptoSetup) ( tls.QUICEncryptionLevel,  []byte) {
	//nolint:exhaustive // handshake records can only be written for Initial and Handshake.
	switch  {
	case tls.QUICEncryptionLevelInitial:
		.events = append(.events, Event{Kind: EventWriteInitialData, Data: })
	case tls.QUICEncryptionLevelHandshake:
		.events = append(.events, Event{Kind: EventWriteHandshakeData, Data: })
	case tls.QUICEncryptionLevelApplication:
		panic("unexpected write")
	default:
		panic(fmt.Sprintf("unexpected write encryption level: %s", ))
	}
}

func ( *cryptoSetup) () {
	 := .initialOpener != nil
	.initialOpener = nil
	.initialSealer = nil
	if  {
		.logger.Debugf("Dropping Initial keys.")
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClientInitial})
			.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeServerInitial})
		}
	}
}

func ( *cryptoSetup) () {
	.handshakeCompleteTime = time.Now()
	.events = append(.events, Event{Kind: EventHandshakeComplete})
}

func ( *cryptoSetup) () {
	.aead.SetHandshakeConfirmed()
	// drop Handshake keys
	var  bool
	if .handshakeOpener != nil {
		.handshakeOpener = nil
		.handshakeSealer = nil
		 = true
	}
	if  {
		.logger.Debugf("Dropping Handshake keys.")
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClientHandshake})
			.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeServerHandshake})
		}
	}
}

func ( *cryptoSetup) () (LongHeaderSealer, error) {
	if .initialSealer == nil {
		return nil, ErrKeysDropped
	}
	return .initialSealer, nil
}

func ( *cryptoSetup) () (LongHeaderSealer, error) {
	if .zeroRTTSealer == nil {
		return nil, ErrKeysDropped
	}
	return .zeroRTTSealer, nil
}

func ( *cryptoSetup) () (LongHeaderSealer, error) {
	if .handshakeSealer == nil {
		if .initialSealer == nil {
			return nil, ErrKeysDropped
		}
		return nil, ErrKeysNotYetAvailable
	}
	return .handshakeSealer, nil
}

func ( *cryptoSetup) () (ShortHeaderSealer, error) {
	if !.has1RTTSealer {
		return nil, ErrKeysNotYetAvailable
	}
	return .aead, nil
}

func ( *cryptoSetup) () (LongHeaderOpener, error) {
	if .initialOpener == nil {
		return nil, ErrKeysDropped
	}
	return .initialOpener, nil
}

func ( *cryptoSetup) () (LongHeaderOpener, error) {
	if .zeroRTTOpener == nil {
		if .initialOpener != nil {
			return nil, ErrKeysNotYetAvailable
		}
		// if the initial opener is also not available, the keys were already dropped
		return nil, ErrKeysDropped
	}
	return .zeroRTTOpener, nil
}

func ( *cryptoSetup) () (LongHeaderOpener, error) {
	if .handshakeOpener == nil {
		if .initialOpener != nil {
			return nil, ErrKeysNotYetAvailable
		}
		// if the initial opener is also not available, the keys were already dropped
		return nil, ErrKeysDropped
	}
	return .handshakeOpener, nil
}

func ( *cryptoSetup) () (ShortHeaderOpener, error) {
	if .zeroRTTOpener != nil && time.Since(.handshakeCompleteTime) > 3*.rttStats.PTO(true) {
		.zeroRTTOpener = nil
		.logger.Debugf("Dropping 0-RTT keys.")
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClient0RTT})
		}
	}

	if !.has1RTTOpener {
		return nil, ErrKeysNotYetAvailable
	}
	return .aead, nil
}

func ( *cryptoSetup) () ConnectionState {
	return ConnectionState{
		ConnectionState: .conn.ConnectionState(),
		Used0RTT:        .used0RTT.Load(),
	}
}

func wrapError( error) error {
	if  := tls.AlertError(0); errors.As(, &) {
		return qerr.NewLocalCryptoError(uint8(), )
	}
	return &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: .Error()}
}

func encLevelToKeyType( protocol.EncryptionLevel,  protocol.Perspective) qlog.KeyType {
	if  == protocol.PerspectiveServer {
		switch  {
		case protocol.EncryptionInitial:
			return qlog.KeyTypeServerInitial
		case protocol.EncryptionHandshake:
			return qlog.KeyTypeServerHandshake
		case protocol.Encryption0RTT:
			return qlog.KeyTypeServer0RTT
		case protocol.Encryption1RTT:
			return qlog.KeyTypeServer1RTT
		default:
			return ""
		}
	}
	switch  {
	case protocol.EncryptionInitial:
		return qlog.KeyTypeClientInitial
	case protocol.EncryptionHandshake:
		return qlog.KeyTypeClientHandshake
	case protocol.Encryption0RTT:
		return qlog.KeyTypeClient0RTT
	case protocol.Encryption1RTT:
		return qlog.KeyTypeClient1RTT
	default:
		return ""
	}
}