package relayproto

import (
	
	
	
	
	

	
	
	
)

// domainSepChallenge is the blake3 derive-key context for the challenge
// signature, matching iroh-relay/src/protos/handshake.rs.
const domainSepChallenge = "iroh-relay handshake v1 challenge signature"

// ClientAuthHeader is the HTTP header carrying TLS key-material relay auth.
const ClientAuthHeader = "x-iroh-relay-client-auth-v1"

const domainSepTLSExportLabel = "iroh-relay handshake v1"

// Handshake errors.
var (
	ErrServerDeniedAuth   = errors.New("relayproto: the relay denied authentication")
	ErrSignatureInvalid   = errors.New("relayproto: client signature invalid")
	ErrHandshakeDeserial  = errors.New("relayproto: handshake frame deserialization failed")
	ErrUnexpectedFrameTag = errors.New("relayproto: unexpected handshake frame type")
	ErrNoKeyingMaterial   = errors.New("relayproto: no TLS keying material")
	ErrKeyMaterialSuffix  = errors.New("relayproto: TLS keying material suffix mismatch")
)

// ServerChallenge is the challenge a relay sends a client to sign for endpoint
// authentication.
type ServerChallenge struct {
	// Challenge is 16 random bytes the client must sign.
	Challenge [16]byte
}

// messageToSign derives the actual 32-byte message signed for this challenge.
// The client signs a derived key rather than the challenge directly, for domain
// separation (see the Rust source for the rationale).
func ( ServerChallenge) () [32]byte {
	var  [32]byte
	blake3.DeriveKey([:], domainSepChallenge, .Challenge[:])
	return 
}

// AppendTo appends the framed wire encoding (frame type + postcard body) of c.
func ( ServerChallenge) ( []byte) []byte {
	 = writeFrameType(, FrameServerChallenge)
	return append(, .Challenge[:]...) // [u8;16]: 16 raw bytes, no length prefix
}

// ClientAuth is the client's authentication response: its public key and a
// signature of the challenge's message-to-sign.
type ClientAuth struct {
	PublicKey key.PublicKey
	Signature key.Signature
}

// KeyMaterialClientAuth is the client's 1-RTT relay authentication. It is sent
// in [ClientAuthHeader] as base64url-no-pad postcard bytes.
type KeyMaterialClientAuth struct {
	PublicKey         key.PublicKey
	Signature         key.Signature
	KeyMaterialSuffix [16]byte
}

// NewClientAuth builds a ClientAuth for challenge using secretKey.
func ( key.SecretKey,  ServerChallenge) ClientAuth {
	 := .messageToSign()
	return ClientAuth{
		PublicKey: .Public(),
		Signature: .Sign([:]),
	}
}

// Verify checks this client auth against the challenge it answers.
func ( ClientAuth) ( ServerChallenge) error {
	 := .messageToSign()
	if  := .PublicKey.Verify([:], .Signature);  != nil {
		return fmt.Errorf("%w: %v", ErrSignatureInvalid, )
	}
	return nil
}

// NewKeyMaterialClientAuth builds a client auth header value from exported TLS
// keying material. It returns [ErrNoKeyingMaterial] if state cannot export it.
func ( key.SecretKey,  *tls.ConnectionState) (KeyMaterialClientAuth, error) {
	if  == nil {
		return KeyMaterialClientAuth{}, ErrNoKeyingMaterial
	}
	 := .Public()
	 := .Bytes()
	,  := .ExportKeyingMaterial(domainSepTLSExportLabel, [:], 32)
	if  != nil {
		return KeyMaterialClientAuth{}, fmt.Errorf("%w: %v", ErrNoKeyingMaterial, )
	}
	 := KeyMaterialClientAuth{
		PublicKey: ,
		Signature: .Sign([:16]),
	}
	copy(.KeyMaterialSuffix[:], [16:])
	return , nil
}

// KeyMaterialClientAuthFromHeader decodes a value from [ClientAuthHeader].
func ( string) (KeyMaterialClientAuth, error) {
	,  := base64.RawURLEncoding.DecodeString()
	if  != nil {
		return KeyMaterialClientAuth{}, fmt.Errorf("%w: %v", ErrHandshakeDeserial, )
	}
	var  KeyMaterialClientAuth
	if  := postcard.Unmarshal(, &);  != nil {
		return KeyMaterialClientAuth{}, fmt.Errorf("%w: %v", ErrHandshakeDeserial, )
	}
	return , nil
}

// HeaderValue encodes a for [ClientAuthHeader].
func ( KeyMaterialClientAuth) () (string, error) {
	,  := postcard.Marshal()
	if  != nil {
		return "", fmt.Errorf("relayproto: encode key-material auth: %w", )
	}
	return base64.RawURLEncoding.EncodeToString(), nil
}

// Verify checks this key-material auth against the server's TLS state.
func ( KeyMaterialClientAuth) ( *tls.ConnectionState) error {
	if  == nil {
		return ErrNoKeyingMaterial
	}
	 := .PublicKey.Bytes()
	,  := .ExportKeyingMaterial(domainSepTLSExportLabel, [:], 32)
	if  != nil {
		return fmt.Errorf("%w: %v", ErrNoKeyingMaterial, )
	}
	if !bytes.Equal([16:], .KeyMaterialSuffix[:]) {
		return ErrKeyMaterialSuffix
	}
	if  := .PublicKey.Verify([:16], .Signature);  != nil {
		return fmt.Errorf("%w: %v", ErrSignatureInvalid, )
	}
	return nil
}

// EncodePostcard encodes a like Rust KeyMaterialClientAuth: raw public key,
// serde_bytes signature, then raw 16-byte suffix.
func ( KeyMaterialClientAuth) ( *postcard.Encoder) error {
	 := .PublicKey.Bytes()
	.RawBytes([:])
	 := .Signature.Bytes()
	.BytesValue([:])
	.RawBytes(.KeyMaterialSuffix[:])
	return nil
}

// DecodePostcard decodes a Rust KeyMaterialClientAuth.
func ( *KeyMaterialClientAuth) ( *postcard.Decoder) error {
	,  := .RawBytes(key.PublicKeySize)
	if  != nil {
		return 
	}
	,  := key.PublicKeyFromSlice()
	if  != nil {
		return 
	}
	,  := .BytesValue()
	if  != nil {
		return 
	}
	,  := key.SignatureFromSlice()
	if  != nil {
		return 
	}
	,  := .RawBytes(16)
	if  != nil {
		return 
	}
	.PublicKey = 
	.Signature = 
	copy(.KeyMaterialSuffix[:], )
	return nil
}

// AppendTo appends the framed wire encoding of a: frame type, the public key's
// 32 raw bytes, then the signature as a postcard serde_bytes value (varint
// length 64 followed by the 64 bytes).
func ( ClientAuth) ( []byte) []byte {
	 = writeFrameType(, FrameClientAuth)
	 := .PublicKey.Bytes()
	 = append(, [:]...) // PublicKey: 32 raw bytes (non-human-readable serde)
	 := .Signature.Bytes()
	 = appendPostcardVarint(, uint64(len())) // serde_bytes: postcard length prefix
	return append(, [:]...)
}

// ServerConfirmsAuth confirms a successful connection. Its postcard body is empty.
type ServerConfirmsAuth struct{}

// AppendTo appends the framed wire encoding of the (empty-bodied) confirmation.
func (ServerConfirmsAuth) ( []byte) []byte {
	return writeFrameType(, FrameServerConfirmsAuth)
}

// ServerDeniesAuth denies a connection with a reason.
type ServerDeniesAuth struct {
	Reason string
}

// AppendTo appends the framed wire encoding: frame type then the reason as a
// postcard string (varint byte-length prefix + UTF-8 bytes).
func ( ServerDeniesAuth) ( []byte) []byte {
	 = writeFrameType(, FrameServerDeniesAuth)
	 = appendPostcardVarint(, uint64(len(.Reason)))
	return append(, .Reason...)
}

// ParseHandshakeFrame parses one handshake frame from content (frame type +
// postcard body) into the concrete frame value, returning the value as one of
// *ServerChallenge, *ClientAuth, *ServerConfirmsAuth, or *ServerDeniesAuth.
func ( []byte) (any, error) {
	, ,  := readFrameType()
	if  != nil {
		return nil, 
	}
	switch  {
	case FrameServerChallenge:
		if len() != 16 {
			return nil, ErrHandshakeDeserial
		}
		var  ServerChallenge
		copy(.Challenge[:], )
		return &, nil
	case FrameClientAuth:
		if len() < key.PublicKeySize {
			return nil, ErrHandshakeDeserial
		}
		,  := key.PublicKeyFromSlice([:key.PublicKeySize])
		if  != nil {
			return nil, fmt.Errorf("%w: %v", ErrHandshakeDeserial, )
		}
		 = [key.PublicKeySize:]
		, ,  := readPostcardVarint()
		if  != nil ||  != uint64(key.SignatureSize) || len() != key.SignatureSize {
			return nil, ErrHandshakeDeserial
		}
		,  := key.SignatureFromSlice()
		if  != nil {
			return nil, fmt.Errorf("%w: %v", ErrHandshakeDeserial, )
		}
		return &ClientAuth{PublicKey: , Signature: }, nil
	case FrameServerConfirmsAuth:
		return &ServerConfirmsAuth{}, nil
	case FrameServerDeniesAuth:
		, ,  := readPostcardVarint()
		if  != nil ||  != uint64(len()) {
			return nil, ErrHandshakeDeserial
		}
		return &ServerDeniesAuth{Reason: string()}, nil
	default:
		return nil, fmt.Errorf("%w: %s", ErrUnexpectedFrameTag, )
	}
}