package endpointticket

import (
	
	
	
	
	
	
	
	
	

	
	
)

const (
	// Kind is the string prefix for endpoint tickets.
	Kind         = "endpoint"
	wireVariant1 = 0

	// MaxAddrs is the maximum number of addresses accepted in a ticket.
	// It is a memory-exhaustion guard, not a wire-compatibility limit.
	MaxAddrs = 65536
)

var base32NoPad = base32.StdEncoding.WithPadding(base32.NoPadding)

var (
	// ErrTrailingBytes is returned when a ticket has extra data after the
	// endpoint address.
	ErrTrailingBytes = errors.New("endpoint ticket: trailing bytes")
	// ErrTruncated is returned when a ticket ends before a complete field.
	ErrTruncated = errors.New("endpoint ticket: truncated")
	// ErrVarintOverflow is returned when a varint field exceeds 64 bits.
	ErrVarintOverflow = errors.New("endpoint ticket: varint overflow")
)

// TicketCodec is the generic shape of an iroh ticket implementation.
//
// It mirrors Rust's iroh_tickets::Ticket trait: a ticket has a lowercase kind
// prefix, a byte representation, and a canonical string form of kind plus
// base32-without-padding bytes.
type TicketCodec interface {
	Kind() string
	EncodeBytes() []byte
	EncodeString() string
}

// Decoder decodes a ticket from its byte representation.
type Decoder func([]byte) (TicketCodec, error)

// Registry decodes tickets by kind.
type Registry struct {
	mu       sync.RWMutex
	decoders map[string]Decoder
}

// NewRegistry returns an empty ticket decoder registry.
func () *Registry {
	return &Registry{decoders: make(map[string]Decoder)}
}

// Register adds decoder for kind. Kind must be non-empty and unique.
func ( *Registry) ( string,  Decoder) error {
	if  == nil {
		return errors.New("endpoint ticket: nil registry")
	}
	if  == "" {
		return errors.New("endpoint ticket: empty kind")
	}
	if  == nil {
		return errors.New("endpoint ticket: nil decoder")
	}
	.mu.Lock()
	defer .mu.Unlock()
	if .decoders == nil {
		.decoders = make(map[string]Decoder)
	}
	if ,  := .decoders[];  {
		return fmt.Errorf("endpoint ticket: duplicate kind %q", )
	}
	.decoders[] = 
	return nil
}

// DecodeBytes decodes bytes as a ticket of kind.
func ( *Registry) ( string,  []byte) (TicketCodec, error) {
	if  == nil {
		return nil, errors.New("endpoint ticket: nil registry")
	}
	.mu.RLock()
	 := .decoders[]
	.mu.RUnlock()
	if  == nil {
		return nil, &ParseError{Kind: ParseErrorKindKind, Expected: }
	}
	return ()
}

// DecodeString decodes s using the registered kind prefix.
func ( *Registry) ( string) (TicketCodec, error) {
	if  == nil {
		return nil, errors.New("endpoint ticket: nil registry")
	}
	.mu.RLock()
	var  string
	var  Decoder
	for ,  := range .decoders {
		if strings.HasPrefix(, ) && len() > len() {
			,  = , 
		}
	}
	.mu.RUnlock()
	if  == nil {
		return nil, &ParseError{Kind: ParseErrorKindKind}
	}
	 := [len():]
	,  := base32NoPad.DecodeString(strings.ToUpper())
	if  != nil {
		return nil, &ParseError{Kind: ParseErrorKindEncoding, Err: }
	}
	return ()
}

// RegisterEndpoint registers the endpoint ticket decoder in r.
func ( *Registry) error {
	return .Register(Kind, func( []byte) (TicketCodec, error) {
		return DecodeBytes()
	})
}

// ParseErrorKind classifies ticket parse failures.
type ParseErrorKind string

const (
	// ParseErrorKindKind means the ticket string had the wrong kind prefix.
	ParseErrorKindKind ParseErrorKind = "kind"
	// ParseErrorKindEncoding means the ticket payload was not valid base32.
	ParseErrorKindEncoding ParseErrorKind = "encoding"
	// ParseErrorKindPostcard means the payload looked like this ticket kind but
	// did not decode as the ticket byte format.
	ParseErrorKindPostcard ParseErrorKind = "postcard"
	// ParseErrorKindVerify means decoded bytes failed semantic validation.
	ParseErrorKindVerify ParseErrorKind = "verify"
)

// ParseError reports a structured ticket parse failure.
type ParseError struct {
	Kind     ParseErrorKind
	Expected string
	Message  string
	Err      error
}

func ( *ParseError) () string {
	switch .Kind {
	case ParseErrorKindKind:
		return fmt.Sprintf("endpoint ticket: wrong prefix, expected %s", .Expected)
	case ParseErrorKindEncoding:
		if .Err != nil {
			return "endpoint ticket: decode base32: " + .Err.Error()
		}
		return "endpoint ticket: decode base32"
	case ParseErrorKindVerify:
		return "endpoint ticket: verification failed: " + .Message
	default:
		if .Err != nil {
			return "endpoint ticket: decode: " + .Err.Error()
		}
		return "endpoint ticket: decode"
	}
}

func ( *ParseError) () error { return .Err }

func ( *ParseError) ( error) bool {
	,  := .(*ParseError)
	if ! {
		return false
	}
	return .Kind == "" || .Kind == .Kind
}

var (
	// ErrMissingPrefix is returned when a ticket does not start with
	// "endpoint".
	ErrMissingPrefix = &ParseError{Kind: ParseErrorKindKind, Expected: Kind}
	// ErrEncoding is returned when the ticket payload is not valid base32.
	ErrEncoding = &ParseError{Kind: ParseErrorKindEncoding}
	// ErrDecode is returned when the decoded bytes are not a valid endpoint
	// ticket payload.
	ErrDecode = &ParseError{Kind: ParseErrorKindPostcard}
	// ErrVerify is returned when decoded bytes fail semantic validation.
	ErrVerify = &ParseError{Kind: ParseErrorKindVerify}
)

// Ticket is an endpoint ticket.
type Ticket struct {
	addr netaddr.EndpointAddr
}

// New returns a ticket for addr.
func ( netaddr.EndpointAddr) Ticket {
	return Ticket{addr: }
}

// Encode returns the ticket string for addr.
func ( netaddr.EndpointAddr) string {
	return New().String()
}

// EncodeString returns t's canonical string form.
func ( TicketCodec) string {
	return .EncodeString()
}

// Parse parses s as an endpoint ticket.
func ( string) (Ticket, error) {
	,  := Decode()
	if  != nil {
		return Ticket{}, 
	}
	return New(), nil
}

// Decode parses s as an endpoint ticket and returns its endpoint address.
func ( string) (netaddr.EndpointAddr, error) {
	,  := DecodeString()
	if  != nil {
		return netaddr.EndpointAddr{}, 
	}
	return .Addr(), nil
}

// DecodeString parses s as an endpoint ticket.
func ( string) (Ticket, error) {
	,  := strings.CutPrefix(, Kind)
	if ! {
		return Ticket{}, ErrMissingPrefix
	}
	,  := base32NoPad.DecodeString(strings.ToUpper())
	if  != nil {
		return Ticket{}, &ParseError{Kind: ParseErrorKindEncoding, Err: }
	}
	return DecodeBytes()
}

// DecodeBytes decodes an endpoint ticket from its byte representation.
func ( []byte) (Ticket, error) {
	 := parser{b: }
	,  := .varint()
	if  != nil {
		return Ticket{}, wrapDecodeErr()
	}
	if  != wireVariant1 {
		return Ticket{}, &ParseError{Kind: ParseErrorKindVerify, Message: fmt.Sprintf("unsupported variant %d", )}
	}
	,  := .bytes(key.PublicKeySize)
	if  != nil {
		return Ticket{}, wrapDecodeErr()
	}
	,  := key.EndpointIDFromSlice()
	if  != nil {
		return Ticket{}, &ParseError{Kind: ParseErrorKindVerify, Message: "endpoint id", Err: }
	}
	,  := .varint()
	if  != nil {
		return Ticket{}, wrapDecodeErr()
	}
	if  > MaxAddrs {
		return Ticket{}, &ParseError{Kind: ParseErrorKindVerify, Message: fmt.Sprintf("too many addresses %d", )}
	}
	 := make([]netaddr.TransportAddr, 0, )
	for range  {
		,  := .transportAddr()
		if  != nil {
			return Ticket{}, wrapDecodeErr()
		}
		 = append(, )
	}
	if !.done() {
		return Ticket{}, wrapDecodeErr(ErrTrailingBytes)
	}
	return New(netaddr.NewEndpointAddr(, ...)), nil
}

// Addr returns the endpoint address in t.
func ( Ticket) () netaddr.EndpointAddr {
	return .addr
}

// Kind returns the ticket kind prefix.
func ( Ticket) () string { return Kind }

// EncodeBytes returns the ticket's byte representation.
func ( Ticket) () []byte {
	var  []byte
	 = appendVarint(, wireVariant1)
	 := .addr.ID.Bytes()
	 = append(, [:]...)
	 := .addr.Addrs()
	 = appendVarint(, uint64(len()))
	for ,  := range  {
		 = appendTransportAddr(, )
	}
	return 
}

// EncodeString returns the encoded ticket string.
func ( Ticket) () string {
	return Kind + strings.ToLower(base32NoPad.EncodeToString(.EncodeBytes()))
}

// String returns the encoded ticket string.
func ( Ticket) () string {
	return .EncodeString()
}

// MarshalText implements encoding.TextMarshaler.
func ( Ticket) () ([]byte, error) { return []byte(.String()), nil }

// UnmarshalText implements encoding.TextUnmarshaler.
func ( *Ticket) ( []byte) error {
	,  := Parse(string())
	if  != nil {
		return 
	}
	* = 
	return nil
}

// Short returns a ticket containing only the endpoint id and relay URLs.
func ( Ticket) () Ticket {
	return New(ShortAddr(.addr))
}

// Short returns a ticket for addr containing only the endpoint id and relay
// URLs.
func ( netaddr.EndpointAddr) Ticket {
	return New(ShortAddr())
}

// ShortAddr returns addr with direct IP and custom addresses removed.
func ( netaddr.EndpointAddr) netaddr.EndpointAddr {
	var  []netaddr.TransportAddr
	for ,  := range .Addrs() {
		if ,  := .(netaddr.RelayAddr);  {
			 = append(, )
		}
	}
	return netaddr.NewEndpointAddr(.ID, ...)
}

func wrapDecodeErr( error) error {
	if  == nil {
		return nil
	}
	if errors.Is(, ErrTrailingBytes) || errors.Is(, ErrTruncated) || errors.Is(, ErrVarintOverflow) {
		return &ParseError{Kind: ParseErrorKindPostcard, Err: }
	}
	return 
}

func appendTransportAddr( []byte,  netaddr.TransportAddr) []byte {
	switch a := .(type) {
	case netaddr.RelayAddr:
		 = appendVarint(, 0)
		 := []byte(.URL.String())
		 = appendVarint(, uint64(len()))
		return append(, ...)
	case netaddr.IPAddr:
		 = appendVarint(, 1)
		 := .Addr
		if .Addr().Is4() {
			 := .Addr().As4()
			 = appendVarint(, 0)
			 = append(, [:]...)
		} else {
			 := .Addr().As16()
			 = appendVarint(, 1)
			 = append(, [:]...)
		}
		 = appendVarint(, uint64(.Port()))
		if !.Addr().Is4() {
			// Every 16-byte address, including an IPv4-mapped one, is a
			// SocketAddrV6 on the wire and carries flowinfo and scope id;
			// the decoder reads them back unconditionally.
			 = appendVarint(, 0)
			 = appendVarint(, uint64(scopeID(.Addr().Zone())))
		}
		return 
	case netaddr.CustomAddr:
		 = appendVarint(, 2)
		 = appendVarint(, .ID())
		 := .Data()
		 = appendVarint(, uint64(len()))
		return append(, ...)
	default:
		panic("unreachable transport address")
	}
}

func appendVarint( []byte,  uint64) []byte {
	for  >= 0x80 {
		 = append(, byte()|0x80)
		 >>= 7
	}
	return append(, byte())
}

type parser struct {
	b   []byte
	off int
}

func ( *parser) () bool { return .off == len(.b) }

func ( *parser) ( int) ([]byte, error) {
	if  < 0 || len(.b)-.off <  {
		return nil, ErrTruncated
	}
	 := .b[.off : .off+]
	.off += 
	return , nil
}

func ( *parser) () (uint64, error) {
	var  uint64
	for  := uint(0);  < 64;  += 7 {
		,  := .bytes(1)
		if  != nil {
			return 0, 
		}
		 |= uint64([0]&0x7f) << 
		if [0]&0x80 == 0 {
			return , nil
		}
	}
	return 0, ErrVarintOverflow
}

func ( *parser) () (netaddr.TransportAddr, error) {
	,  := .varint()
	if  != nil {
		return nil, 
	}
	switch  {
	case 0:
		,  := .varint()
		if  != nil {
			return nil, 
		}
		,  := .bytes(int())
		if  != nil {
			return nil, 
		}
		,  := netaddr.ParseRelayURL(string())
		if  != nil {
			return nil, 
		}
		return netaddr.RelayAddr{URL: }, nil
	case 1:
		,  := .varint()
		if  != nil {
			return nil, 
		}
		var  netip.Addr
		 := false
		switch  {
		case 0:
			,  := .bytes(4)
			if  != nil {
				return nil, 
			}
			 = netip.AddrFrom4([4]byte())
		case 1:
			,  := .bytes(16)
			if  != nil {
				return nil, 
			}
			 = netip.AddrFrom16([16]byte())
			 = true
		default:
			return nil, fmt.Errorf("endpoint ticket: unsupported IP family %d", )
		}
		,  := .varint()
		if  != nil {
			return nil, 
		}
		if  > 65535 {
			return nil, fmt.Errorf("endpoint ticket: invalid port %d", )
		}
		if  {
			if ,  := .varint();  != nil {
				return nil, 
			}
			,  := .varint()
			if  != nil {
				return nil, 
			}
			if  > 0xffffffff {
				return nil, fmt.Errorf("endpoint ticket: invalid IPv6 scope id %d", )
			}
			if  != 0 {
				 = .WithZone(zoneFromScopeID(uint32()))
			}
		}
		return netaddr.IPAddr{Addr: netip.AddrPortFrom(, uint16())}, nil
	case 2:
		,  := .varint()
		if  != nil {
			return nil, 
		}
		,  := .varint()
		if  != nil {
			return nil, 
		}
		,  := .bytes(int())
		if  != nil {
			return nil, 
		}
		return netaddr.NewCustomAddr(, slices.Clone()), nil
	default:
		return nil, fmt.Errorf("endpoint ticket: unsupported transport kind %d", )
	}
}

func scopeID( string) uint32 {
	if  == "" {
		return 0
	}
	if ,  := strconv.ParseUint(, 10, 32);  == nil {
		return uint32()
	}
	if ,  := net.InterfaceByName();  == nil && .Index > 0 {
		return uint32(.Index)
	}
	return 0
}

func zoneFromScopeID( uint32) string {
	if  == 0 {
		return ""
	}
	if ,  := net.InterfaceByIndex(int());  == nil && .Name != "" {
		return .Name
	}
	return strconv.FormatUint(uint64(), 10)
}