package relayproto

import (
	
	
	

	
)

// ProtocolVersion is the negotiated relay protocol version for a connection.
type ProtocolVersion int

// Relay protocol versions.
const (
	ProtocolV1 ProtocolVersion = 1
	ProtocolV2 ProtocolVersion = 2
)

// Wire identifiers for the relay protocol versions, sent in the
// Sec-WebSocket-Protocol header.
const (
	protocolV1Wire = "iroh-relay-v1"
	protocolV2Wire = "iroh-relay-v2"
)

// SupportedProtocolVersions returns the wire identifiers of all supported
// protocol versions, newest first (the order offered to a relay server).
func () []string {
	return []string{protocolV2Wire, protocolV1Wire}
}

// WireString returns the wire identifier for v.
func ( ProtocolVersion) () string {
	switch  {
	case ProtocolV1:
		return protocolV1Wire
	case ProtocolV2:
		return protocolV2Wire
	default:
		return ""
	}
}

// ParseProtocolVersion parses a protocol version from its wire identifier.
func ( string) (ProtocolVersion, bool) {
	switch  {
	case protocolV1Wire:
		return ProtocolV1, true
	case protocolV2Wire:
		return ProtocolV2, true
	default:
		return 0, false
	}
}

// Status is a one-way relay-to-client message declaring the connection health.
type Status uint8

const (
	// StatusHealthy reports the connection recovered from previous problems.
	StatusHealthy Status = 0
	// StatusSameEndpointIDConnected reports another endpoint connected with the
	// same id; no more messages will be received.
	StatusSameEndpointIDConnected Status = 1
	// StatusRateLimited reports the relay is throttling this client's
	// outbound traffic.
	StatusRateLimited Status = 2
)

// RelayToClientMsg is a message a relay sends to a client. Exactly one of its
// fields is meaningful, selected by Type.
type RelayToClientMsg struct {
	Type FrameType
	// Datagrams / RemoteEndpointID for FrameRelayToClientDatagram(Batch).
	RemoteEndpointID key.EndpointID
	Datagrams        Datagrams
	// EndpointGone for FrameEndpointGone.
	EndpointGone key.EndpointID
	// Status for FrameStatus.
	Status Status
	// Ping/Pong payload for FramePing/FramePong.
	Ping [8]byte
	// Restarting durations for FrameRestarting.
	ReconnectIn time.Duration
	TryFor      time.Duration
	// Health problem text for FrameHealth (deprecated, V1 only).
	Health string
}

// AppendTo appends the wire encoding of m to dst.
func ( RelayToClientMsg) ( []byte) []byte {
	 = writeFrameType(, .frameType())
	switch .Type {
	case FrameRelayToClientDatagram, FrameRelayToClientDatagramBat:
		 := .RemoteEndpointID.Bytes()
		 = append(, [:]...)
		 = .Datagrams.appendTo()
	case FrameEndpointGone:
		 := .EndpointGone.Bytes()
		 = append(, [:]...)
	case FramePing, FramePong:
		 = append(, .Ping[:]...)
	case FrameHealth:
		 = append(, .Health...)
	case FrameRestarting:
		 = binary.BigEndian.AppendUint32(, uint32(.ReconnectIn.Milliseconds()))
		 = binary.BigEndian.AppendUint32(, uint32(.TryFor.Milliseconds()))
	case FrameStatus:
		 = append(, byte(.Status))
	}
	return 
}

// EncodedLen returns the number of bytes AppendTo writes.
func ( RelayToClientMsg) () int {
	 := 0
	switch .Type {
	case FrameRelayToClientDatagram, FrameRelayToClientDatagramBat:
		 = 32 + .Datagrams.encodedLen()
	case FrameEndpointGone:
		 = 32
	case FramePing, FramePong:
		 = 8
	case FrameStatus:
		 = 1
	case FrameRestarting:
		 = 8
	case FrameHealth:
		 = len(.Health)
	}
	return frameTypeEncodedLen(.frameType()) + 
}

// frameType returns the wire frame type for m, deriving the datagram batch
// variant from the segment size.
func ( RelayToClientMsg) () FrameType {
	if .Type == FrameRelayToClientDatagram || .Type == FrameRelayToClientDatagramBat {
		if .Datagrams.isBatch() {
			return FrameRelayToClientDatagramBat
		}
		return FrameRelayToClientDatagram
	}
	return .Type
}

// ParseRelayToClientMsg decodes a relay-to-client message. version gates the
// deprecated Health (V1 only) and Status (V2+) frames.
func ( []byte,  ProtocolVersion) (RelayToClientMsg, error) {
	return parseRelayToClientMsg(, , false)
}

// ParseRelayToClientMsgNoCopy decodes a relay-to-client message without
// copying datagram contents. The returned message aliases content.
func ( []byte,  ProtocolVersion) (RelayToClientMsg, error) {
	return parseRelayToClientMsg(, , true)
}

func parseRelayToClientMsg( []byte,  ProtocolVersion,  bool) (RelayToClientMsg, error) {
	, ,  := readFrameType()
	if  != nil {
		return RelayToClientMsg{}, 
	}
	if len() > MaxPacketSize {
		return RelayToClientMsg{}, fmt.Errorf("%w: %d bytes", ErrFrameTooLarge, len())
	}
	switch  {
	case FrameRelayToClientDatagram, FrameRelayToClientDatagramBat:
		if len() < key.PublicKeySize {
			return RelayToClientMsg{}, ErrInvalidFrame
		}
		,  := key.EndpointIDFromSlice([:key.PublicKeySize])
		if  != nil {
			return RelayToClientMsg{}, 
		}
		,  := parseDatagrams([key.PublicKeySize:],  == FrameRelayToClientDatagramBat, )
		if  != nil {
			return RelayToClientMsg{}, 
		}
		return RelayToClientMsg{Type: , RemoteEndpointID: , Datagrams: }, nil
	case FrameEndpointGone:
		if len() != key.PublicKeySize {
			return RelayToClientMsg{}, ErrInvalidFrame
		}
		,  := key.EndpointIDFromSlice()
		if  != nil {
			return RelayToClientMsg{}, 
		}
		return RelayToClientMsg{Type: , EndpointGone: }, nil
	case FramePing, FramePong:
		if len() != 8 {
			return RelayToClientMsg{}, ErrInvalidFrame
		}
		var  [8]byte
		copy([:], )
		return RelayToClientMsg{Type: , Ping: }, nil
	case FrameHealth:
		if  != ProtocolV1 {
			return RelayToClientMsg{}, ErrFrameNotAllowedInVersion
		}
		return RelayToClientMsg{Type: , Health: string()}, nil
	case FrameRestarting:
		if len() != 8 {
			return RelayToClientMsg{}, ErrInvalidFrame
		}
		 := time.Duration(binary.BigEndian.Uint32([:4])) * time.Millisecond
		 := time.Duration(binary.BigEndian.Uint32([4:])) * time.Millisecond
		return RelayToClientMsg{Type: , ReconnectIn: , TryFor: }, nil
	case FrameStatus:
		if  < ProtocolV2 {
			return RelayToClientMsg{}, ErrFrameNotAllowedInVersion
		}
		if len() < 1 {
			return RelayToClientMsg{}, ErrInvalidFrame
		}
		return RelayToClientMsg{Type: , Status: Status([0])}, nil
	default:
		return RelayToClientMsg{}, fmt.Errorf("%w: %s", ErrUnknownFrameType, )
	}
}

// ClientToRelayMsg is a message a client sends to a relay. Exactly one of its
// fields is meaningful, selected by Type.
type ClientToRelayMsg struct {
	Type FrameType
	// DstEndpointID / Datagrams for FrameClientToRelayDatagram(Batch).
	DstEndpointID key.EndpointID
	Datagrams     Datagrams
	// Ping/Pong payload for FramePing/FramePong.
	Ping [8]byte
}

// AppendTo appends the wire encoding of m to dst.
func ( ClientToRelayMsg) ( []byte) []byte {
	 = writeFrameType(, .frameType())
	switch .Type {
	case FrameClientToRelayDatagram, FrameClientToRelayDatagramBat:
		 := .DstEndpointID.Bytes()
		 = append(, [:]...)
		 = .Datagrams.appendTo()
	case FramePing, FramePong:
		 = append(, .Ping[:]...)
	}
	return 
}

// EncodedLen returns the number of bytes AppendTo writes.
func ( ClientToRelayMsg) () int {
	 := 0
	switch .Type {
	case FrameClientToRelayDatagram, FrameClientToRelayDatagramBat:
		 = 32 + .Datagrams.encodedLen()
	case FramePing, FramePong:
		 = 8
	}
	return frameTypeEncodedLen(.frameType()) + 
}

func ( ClientToRelayMsg) () FrameType {
	if .Type == FrameClientToRelayDatagram || .Type == FrameClientToRelayDatagramBat {
		if .Datagrams.isBatch() {
			return FrameClientToRelayDatagramBat
		}
		return FrameClientToRelayDatagram
	}
	return .Type
}

// ParseClientToRelayMsg decodes a client-to-relay message.
func ( []byte) (ClientToRelayMsg, error) {
	return parseClientToRelayMsg(, false)
}

// ParseClientToRelayMsgNoCopy decodes a client-to-relay message without
// copying datagram contents. The returned message aliases content.
func ( []byte) (ClientToRelayMsg, error) {
	return parseClientToRelayMsg(, true)
}

func parseClientToRelayMsg( []byte,  bool) (ClientToRelayMsg, error) {
	, ,  := readFrameType()
	if  != nil {
		return ClientToRelayMsg{}, 
	}
	if len() > MaxPacketSize {
		return ClientToRelayMsg{}, fmt.Errorf("%w: %d bytes", ErrFrameTooLarge, len())
	}
	switch  {
	case FrameClientToRelayDatagram, FrameClientToRelayDatagramBat:
		if len() < key.PublicKeySize {
			return ClientToRelayMsg{}, ErrInvalidFrame
		}
		,  := key.EndpointIDFromSlice([:key.PublicKeySize])
		if  != nil {
			return ClientToRelayMsg{}, 
		}
		,  := parseDatagrams([key.PublicKeySize:],  == FrameClientToRelayDatagramBat, )
		if  != nil {
			return ClientToRelayMsg{}, 
		}
		return ClientToRelayMsg{Type: , DstEndpointID: , Datagrams: }, nil
	case FramePing, FramePong:
		if len() != 8 {
			return ClientToRelayMsg{}, ErrInvalidFrame
		}
		var  [8]byte
		copy([:], )
		return ClientToRelayMsg{Type: , Ping: }, nil
	default:
		return ClientToRelayMsg{}, fmt.Errorf("%w: %s", ErrUnknownFrameType, )
	}
}

func parseDatagrams( []byte, ,  bool) (Datagrams, error) {
	if  {
		return datagramsFromBytesNoCopy(, )
	}
	return datagramsFromBytes(, )
}