package endpointticket
import (
"encoding/base32"
"errors"
"fmt"
"net"
"net/netip"
"slices"
"strconv"
"strings"
"sync"
"github.com/tmc/go-iroh/key"
"github.com/tmc/go-iroh/netaddr"
)
const (
Kind = "endpoint"
wireVariant1 = 0
MaxAddrs = 65536
)
var base32NoPad = base32 .StdEncoding .WithPadding (base32 .NoPadding )
var (
ErrTrailingBytes = errors .New ("endpoint ticket: trailing bytes" )
ErrTruncated = errors .New ("endpoint ticket: truncated" )
ErrVarintOverflow = errors .New ("endpoint ticket: varint overflow" )
)
type TicketCodec interface {
Kind () string
EncodeBytes () []byte
EncodeString () string
}
type Decoder func ([]byte ) (TicketCodec , error )
type Registry struct {
mu sync .RWMutex
decoders map [string ]Decoder
}
func NewRegistry () *Registry {
return &Registry {decoders : make (map [string ]Decoder )}
}
func (r *Registry ) Register (kind string , decoder Decoder ) error {
if r == nil {
return errors .New ("endpoint ticket: nil registry" )
}
if kind == "" {
return errors .New ("endpoint ticket: empty kind" )
}
if decoder == nil {
return errors .New ("endpoint ticket: nil decoder" )
}
r .mu .Lock ()
defer r .mu .Unlock ()
if r .decoders == nil {
r .decoders = make (map [string ]Decoder )
}
if _ , ok := r .decoders [kind ]; ok {
return fmt .Errorf ("endpoint ticket: duplicate kind %q" , kind )
}
r .decoders [kind ] = decoder
return nil
}
func (r *Registry ) DecodeBytes (kind string , b []byte ) (TicketCodec , error ) {
if r == nil {
return nil , errors .New ("endpoint ticket: nil registry" )
}
r .mu .RLock ()
decoder := r .decoders [kind ]
r .mu .RUnlock ()
if decoder == nil {
return nil , &ParseError {Kind : ParseErrorKindKind , Expected : kind }
}
return decoder (b )
}
func (r *Registry ) DecodeString (s string ) (TicketCodec , error ) {
if r == nil {
return nil , errors .New ("endpoint ticket: nil registry" )
}
r .mu .RLock ()
var kind string
var decoder Decoder
for k , d := range r .decoders {
if strings .HasPrefix (s , k ) && len (k ) > len (kind ) {
kind , decoder = k , d
}
}
r .mu .RUnlock ()
if decoder == nil {
return nil , &ParseError {Kind : ParseErrorKindKind }
}
rest := s [len (kind ):]
b , err := base32NoPad .DecodeString (strings .ToUpper (rest ))
if err != nil {
return nil , &ParseError {Kind : ParseErrorKindEncoding , Err : err }
}
return decoder (b )
}
func RegisterEndpoint (r *Registry ) error {
return r .Register (Kind , func (b []byte ) (TicketCodec , error ) {
return DecodeBytes (b )
})
}
type ParseErrorKind string
const (
ParseErrorKindKind ParseErrorKind = "kind"
ParseErrorKindEncoding ParseErrorKind = "encoding"
ParseErrorKindPostcard ParseErrorKind = "postcard"
ParseErrorKindVerify ParseErrorKind = "verify"
)
type ParseError struct {
Kind ParseErrorKind
Expected string
Message string
Err error
}
func (e *ParseError ) Error () string {
switch e .Kind {
case ParseErrorKindKind :
return fmt .Sprintf ("endpoint ticket: wrong prefix, expected %s" , e .Expected )
case ParseErrorKindEncoding :
if e .Err != nil {
return "endpoint ticket: decode base32: " + e .Err .Error()
}
return "endpoint ticket: decode base32"
case ParseErrorKindVerify :
return "endpoint ticket: verification failed: " + e .Message
default :
if e .Err != nil {
return "endpoint ticket: decode: " + e .Err .Error()
}
return "endpoint ticket: decode"
}
}
func (e *ParseError ) Unwrap () error { return e .Err }
func (e *ParseError ) Is (target error ) bool {
t , ok := target .(*ParseError )
if !ok {
return false
}
return t .Kind == "" || e .Kind == t .Kind
}
var (
ErrMissingPrefix = &ParseError {Kind : ParseErrorKindKind , Expected : Kind }
ErrEncoding = &ParseError {Kind : ParseErrorKindEncoding }
ErrDecode = &ParseError {Kind : ParseErrorKindPostcard }
ErrVerify = &ParseError {Kind : ParseErrorKindVerify }
)
type Ticket struct {
addr netaddr .EndpointAddr
}
func New (addr netaddr .EndpointAddr ) Ticket {
return Ticket {addr : addr }
}
func Encode (addr netaddr .EndpointAddr ) string {
return New (addr ).String ()
}
func EncodeString (t TicketCodec ) string {
return t .EncodeString ()
}
func Parse (s string ) (Ticket , error ) {
addr , err := Decode (s )
if err != nil {
return Ticket {}, err
}
return New (addr ), nil
}
func Decode (s string ) (netaddr .EndpointAddr , error ) {
t , err := DecodeString (s )
if err != nil {
return netaddr .EndpointAddr {}, err
}
return t .Addr (), nil
}
func DecodeString (s string ) (Ticket , error ) {
rest , ok := strings .CutPrefix (s , Kind )
if !ok {
return Ticket {}, ErrMissingPrefix
}
b , err := base32NoPad .DecodeString (strings .ToUpper (rest ))
if err != nil {
return Ticket {}, &ParseError {Kind : ParseErrorKindEncoding , Err : err }
}
return DecodeBytes (b )
}
func DecodeBytes (b []byte ) (Ticket , error ) {
p := parser {b : b }
variant , err := p .varint ()
if err != nil {
return Ticket {}, wrapDecodeErr (err )
}
if variant != wireVariant1 {
return Ticket {}, &ParseError {Kind : ParseErrorKindVerify , Message : fmt .Sprintf ("unsupported variant %d" , variant )}
}
idBytes , err := p .bytes (key .PublicKeySize )
if err != nil {
return Ticket {}, wrapDecodeErr (err )
}
id , err := key .EndpointIDFromSlice (idBytes )
if err != nil {
return Ticket {}, &ParseError {Kind : ParseErrorKindVerify , Message : "endpoint id" , Err : err }
}
n , err := p .varint ()
if err != nil {
return Ticket {}, wrapDecodeErr (err )
}
if n > MaxAddrs {
return Ticket {}, &ParseError {Kind : ParseErrorKindVerify , Message : fmt .Sprintf ("too many addresses %d" , n )}
}
addrs := make ([]netaddr .TransportAddr , 0 , n )
for range n {
a , err := p .transportAddr ()
if err != nil {
return Ticket {}, wrapDecodeErr (err )
}
addrs = append (addrs , a )
}
if !p .done () {
return Ticket {}, wrapDecodeErr (ErrTrailingBytes )
}
return New (netaddr .NewEndpointAddr (id , addrs ...)), nil
}
func (t Ticket ) Addr () netaddr .EndpointAddr {
return t .addr
}
func (t Ticket ) Kind () string { return Kind }
func (t Ticket ) EncodeBytes () []byte {
var b []byte
b = appendVarint (b , wireVariant1 )
id := t .addr .ID .Bytes ()
b = append (b , id [:]...)
addrs := t .addr .Addrs ()
b = appendVarint (b , uint64 (len (addrs )))
for _ , a := range addrs {
b = appendTransportAddr (b , a )
}
return b
}
func (t Ticket ) EncodeString () string {
return Kind + strings .ToLower (base32NoPad .EncodeToString (t .EncodeBytes ()))
}
func (t Ticket ) String () string {
return t .EncodeString ()
}
func (t Ticket ) MarshalText () ([]byte , error ) { return []byte (t .String ()), nil }
func (t *Ticket ) UnmarshalText (text []byte ) error {
tt , err := Parse (string (text ))
if err != nil {
return err
}
*t = tt
return nil
}
func (t Ticket ) Short () Ticket {
return New (ShortAddr (t .addr ))
}
func Short (addr netaddr .EndpointAddr ) Ticket {
return New (ShortAddr (addr ))
}
func ShortAddr (addr netaddr .EndpointAddr ) netaddr .EndpointAddr {
var addrs []netaddr .TransportAddr
for _ , a := range addr .Addrs () {
if _ , ok := a .(netaddr .RelayAddr ); ok {
addrs = append (addrs , a )
}
}
return netaddr .NewEndpointAddr (addr .ID , addrs ...)
}
func wrapDecodeErr(err error ) error {
if err == nil {
return nil
}
if errors .Is (err , ErrTrailingBytes ) || errors .Is (err , ErrTruncated ) || errors .Is (err , ErrVarintOverflow ) {
return &ParseError {Kind : ParseErrorKindPostcard , Err : err }
}
return err
}
func appendTransportAddr(b []byte , a netaddr .TransportAddr ) []byte {
switch a := a .(type ) {
case netaddr .RelayAddr :
b = appendVarint (b , 0 )
s := []byte (a .URL .String ())
b = appendVarint (b , uint64 (len (s )))
return append (b , s ...)
case netaddr .IPAddr :
b = appendVarint (b , 1 )
ap := a .Addr
if ap .Addr ().Is4 () {
ip4 := ap .Addr ().As4 ()
b = appendVarint (b , 0 )
b = append (b , ip4 [:]...)
} else {
ip6 := ap .Addr ().As16 ()
b = appendVarint (b , 1 )
b = append (b , ip6 [:]...)
}
b = appendVarint (b , uint64 (ap .Port ()))
if !ap .Addr ().Is4 () {
b = appendVarint (b , 0 )
b = appendVarint (b , uint64 (scopeID (ap .Addr ().Zone ())))
}
return b
case netaddr .CustomAddr :
b = appendVarint (b , 2 )
b = appendVarint (b , a .ID ())
data := a .Data ()
b = appendVarint (b , uint64 (len (data )))
return append (b , data ...)
default :
panic ("unreachable transport address" )
}
}
func appendVarint(b []byte , n uint64 ) []byte {
for n >= 0x80 {
b = append (b , byte (n )|0x80 )
n >>= 7
}
return append (b , byte (n ))
}
type parser struct {
b []byte
off int
}
func (p *parser ) done () bool { return p .off == len (p .b ) }
func (p *parser ) bytes (n int ) ([]byte , error ) {
if n < 0 || len (p .b )-p .off < n {
return nil , ErrTruncated
}
out := p .b [p .off : p .off +n ]
p .off += n
return out , nil
}
func (p *parser ) varint () (uint64 , error ) {
var n uint64
for shift := uint (0 ); shift < 64 ; shift += 7 {
b , err := p .bytes (1 )
if err != nil {
return 0 , err
}
n |= uint64 (b [0 ]&0x7f ) << shift
if b [0 ]&0x80 == 0 {
return n , nil
}
}
return 0 , ErrVarintOverflow
}
func (p *parser ) transportAddr () (netaddr .TransportAddr , error ) {
kind , err := p .varint ()
if err != nil {
return nil , err
}
switch kind {
case 0 :
n , err := p .varint ()
if err != nil {
return nil , err
}
s , err := p .bytes (int (n ))
if err != nil {
return nil , err
}
u , err := netaddr .ParseRelayURL (string (s ))
if err != nil {
return nil , err
}
return netaddr .RelayAddr {URL : u }, nil
case 1 :
family , err := p .varint ()
if err != nil {
return nil , err
}
var ip netip .Addr
v6 := false
switch family {
case 0 :
b , err := p .bytes (4 )
if err != nil {
return nil , err
}
ip = netip .AddrFrom4 ([4 ]byte (b ))
case 1 :
b , err := p .bytes (16 )
if err != nil {
return nil , err
}
ip = netip .AddrFrom16 ([16 ]byte (b ))
v6 = true
default :
return nil , fmt .Errorf ("endpoint ticket: unsupported IP family %d" , family )
}
port , err := p .varint ()
if err != nil {
return nil , err
}
if port > 65535 {
return nil , fmt .Errorf ("endpoint ticket: invalid port %d" , port )
}
if v6 {
if _ , err := p .varint (); err != nil {
return nil , err
}
scope , err := p .varint ()
if err != nil {
return nil , err
}
if scope > 0xffffffff {
return nil , fmt .Errorf ("endpoint ticket: invalid IPv6 scope id %d" , scope )
}
if scope != 0 {
ip = ip .WithZone (zoneFromScopeID (uint32 (scope )))
}
}
return netaddr .IPAddr {Addr : netip .AddrPortFrom (ip , uint16 (port ))}, nil
case 2 :
id , err := p .varint ()
if err != nil {
return nil , err
}
n , err := p .varint ()
if err != nil {
return nil , err
}
data , err := p .bytes (int (n ))
if err != nil {
return nil , err
}
return netaddr .NewCustomAddr (id , slices .Clone (data )), nil
default :
return nil , fmt .Errorf ("endpoint ticket: unsupported transport kind %d" , kind )
}
}
func scopeID(zone string ) uint32 {
if zone == "" {
return 0
}
if n , err := strconv .ParseUint (zone , 10 , 32 ); err == nil {
return uint32 (n )
}
if iface , err := net .InterfaceByName (zone ); err == nil && iface .Index > 0 {
return uint32 (iface .Index )
}
return 0
}
func zoneFromScopeID(scope uint32 ) string {
if scope == 0 {
return ""
}
if iface , err := net .InterfaceByIndex (int (scope )); err == nil && iface .Name != "" {
return iface .Name
}
return strconv .FormatUint (uint64 (scope ), 10 )
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .