package relayproto
import (
"encoding/binary"
"fmt"
"time"
"github.com/tmc/go-iroh/key"
)
type ProtocolVersion int
const (
ProtocolV1 ProtocolVersion = 1
ProtocolV2 ProtocolVersion = 2
)
const (
protocolV1Wire = "iroh-relay-v1"
protocolV2Wire = "iroh-relay-v2"
)
func SupportedProtocolVersions () []string {
return []string {protocolV2Wire , protocolV1Wire }
}
func (v ProtocolVersion ) WireString () string {
switch v {
case ProtocolV1 :
return protocolV1Wire
case ProtocolV2 :
return protocolV2Wire
default :
return ""
}
}
func ParseProtocolVersion (s string ) (ProtocolVersion , bool ) {
switch s {
case protocolV1Wire :
return ProtocolV1 , true
case protocolV2Wire :
return ProtocolV2 , true
default :
return 0 , false
}
}
type Status uint8
const (
StatusHealthy Status = 0
StatusSameEndpointIDConnected Status = 1
StatusRateLimited Status = 2
)
type RelayToClientMsg struct {
Type FrameType
RemoteEndpointID key .EndpointID
Datagrams Datagrams
EndpointGone key .EndpointID
Status Status
Ping [8 ]byte
ReconnectIn time .Duration
TryFor time .Duration
Health string
}
func (m RelayToClientMsg ) AppendTo (dst []byte ) []byte {
dst = writeFrameType (dst , m .frameType ())
switch m .Type {
case FrameRelayToClientDatagram , FrameRelayToClientDatagramBat :
id := m .RemoteEndpointID .Bytes ()
dst = append (dst , id [:]...)
dst = m .Datagrams .appendTo (dst )
case FrameEndpointGone :
id := m .EndpointGone .Bytes ()
dst = append (dst , id [:]...)
case FramePing , FramePong :
dst = append (dst , m .Ping [:]...)
case FrameHealth :
dst = append (dst , m .Health ...)
case FrameRestarting :
dst = binary .BigEndian .AppendUint32 (dst , uint32 (m .ReconnectIn .Milliseconds ()))
dst = binary .BigEndian .AppendUint32 (dst , uint32 (m .TryFor .Milliseconds ()))
case FrameStatus :
dst = append (dst , byte (m .Status ))
}
return dst
}
func (m RelayToClientMsg ) EncodedLen () int {
payload := 0
switch m .Type {
case FrameRelayToClientDatagram , FrameRelayToClientDatagramBat :
payload = 32 + m .Datagrams .encodedLen ()
case FrameEndpointGone :
payload = 32
case FramePing , FramePong :
payload = 8
case FrameStatus :
payload = 1
case FrameRestarting :
payload = 8
case FrameHealth :
payload = len (m .Health )
}
return frameTypeEncodedLen (m .frameType ()) + payload
}
func (m RelayToClientMsg ) frameType () FrameType {
if m .Type == FrameRelayToClientDatagram || m .Type == FrameRelayToClientDatagramBat {
if m .Datagrams .isBatch () {
return FrameRelayToClientDatagramBat
}
return FrameRelayToClientDatagram
}
return m .Type
}
func ParseRelayToClientMsg (content []byte , version ProtocolVersion ) (RelayToClientMsg , error ) {
return parseRelayToClientMsg (content , version , false )
}
func ParseRelayToClientMsgNoCopy (content []byte , version ProtocolVersion ) (RelayToClientMsg , error ) {
return parseRelayToClientMsg (content , version , true )
}
func parseRelayToClientMsg(content []byte , version ProtocolVersion , noCopy bool ) (RelayToClientMsg , error ) {
ft , rest , err := readFrameType (content )
if err != nil {
return RelayToClientMsg {}, err
}
if len (rest ) > MaxPacketSize {
return RelayToClientMsg {}, fmt .Errorf ("%w: %d bytes" , ErrFrameTooLarge , len (rest ))
}
switch ft {
case FrameRelayToClientDatagram , FrameRelayToClientDatagramBat :
if len (rest ) < key .PublicKeySize {
return RelayToClientMsg {}, ErrInvalidFrame
}
id , err := key .EndpointIDFromSlice (rest [:key .PublicKeySize ])
if err != nil {
return RelayToClientMsg {}, err
}
dg , err := parseDatagrams (rest [key .PublicKeySize :], ft == FrameRelayToClientDatagramBat , noCopy )
if err != nil {
return RelayToClientMsg {}, err
}
return RelayToClientMsg {Type : ft , RemoteEndpointID : id , Datagrams : dg }, nil
case FrameEndpointGone :
if len (rest ) != key .PublicKeySize {
return RelayToClientMsg {}, ErrInvalidFrame
}
id , err := key .EndpointIDFromSlice (rest )
if err != nil {
return RelayToClientMsg {}, err
}
return RelayToClientMsg {Type : ft , EndpointGone : id }, nil
case FramePing , FramePong :
if len (rest ) != 8 {
return RelayToClientMsg {}, ErrInvalidFrame
}
var p [8 ]byte
copy (p [:], rest )
return RelayToClientMsg {Type : ft , Ping : p }, nil
case FrameHealth :
if version != ProtocolV1 {
return RelayToClientMsg {}, ErrFrameNotAllowedInVersion
}
return RelayToClientMsg {Type : ft , Health : string (rest )}, nil
case FrameRestarting :
if len (rest ) != 8 {
return RelayToClientMsg {}, ErrInvalidFrame
}
reconnect := time .Duration (binary .BigEndian .Uint32 (rest [:4 ])) * time .Millisecond
tryFor := time .Duration (binary .BigEndian .Uint32 (rest [4 :])) * time .Millisecond
return RelayToClientMsg {Type : ft , ReconnectIn : reconnect , TryFor : tryFor }, nil
case FrameStatus :
if version < ProtocolV2 {
return RelayToClientMsg {}, ErrFrameNotAllowedInVersion
}
if len (rest ) < 1 {
return RelayToClientMsg {}, ErrInvalidFrame
}
return RelayToClientMsg {Type : ft , Status : Status (rest [0 ])}, nil
default :
return RelayToClientMsg {}, fmt .Errorf ("%w: %s" , ErrUnknownFrameType , ft )
}
}
type ClientToRelayMsg struct {
Type FrameType
DstEndpointID key .EndpointID
Datagrams Datagrams
Ping [8 ]byte
}
func (m ClientToRelayMsg ) AppendTo (dst []byte ) []byte {
dst = writeFrameType (dst , m .frameType ())
switch m .Type {
case FrameClientToRelayDatagram , FrameClientToRelayDatagramBat :
id := m .DstEndpointID .Bytes ()
dst = append (dst , id [:]...)
dst = m .Datagrams .appendTo (dst )
case FramePing , FramePong :
dst = append (dst , m .Ping [:]...)
}
return dst
}
func (m ClientToRelayMsg ) EncodedLen () int {
payload := 0
switch m .Type {
case FrameClientToRelayDatagram , FrameClientToRelayDatagramBat :
payload = 32 + m .Datagrams .encodedLen ()
case FramePing , FramePong :
payload = 8
}
return frameTypeEncodedLen (m .frameType ()) + payload
}
func (m ClientToRelayMsg ) frameType () FrameType {
if m .Type == FrameClientToRelayDatagram || m .Type == FrameClientToRelayDatagramBat {
if m .Datagrams .isBatch () {
return FrameClientToRelayDatagramBat
}
return FrameClientToRelayDatagram
}
return m .Type
}
func ParseClientToRelayMsg (content []byte ) (ClientToRelayMsg , error ) {
return parseClientToRelayMsg (content , false )
}
func ParseClientToRelayMsgNoCopy (content []byte ) (ClientToRelayMsg , error ) {
return parseClientToRelayMsg (content , true )
}
func parseClientToRelayMsg(content []byte , noCopy bool ) (ClientToRelayMsg , error ) {
ft , rest , err := readFrameType (content )
if err != nil {
return ClientToRelayMsg {}, err
}
if len (rest ) > MaxPacketSize {
return ClientToRelayMsg {}, fmt .Errorf ("%w: %d bytes" , ErrFrameTooLarge , len (rest ))
}
switch ft {
case FrameClientToRelayDatagram , FrameClientToRelayDatagramBat :
if len (rest ) < key .PublicKeySize {
return ClientToRelayMsg {}, ErrInvalidFrame
}
id , err := key .EndpointIDFromSlice (rest [:key .PublicKeySize ])
if err != nil {
return ClientToRelayMsg {}, err
}
dg , err := parseDatagrams (rest [key .PublicKeySize :], ft == FrameClientToRelayDatagramBat , noCopy )
if err != nil {
return ClientToRelayMsg {}, err
}
return ClientToRelayMsg {Type : ft , DstEndpointID : id , Datagrams : dg }, nil
case FramePing , FramePong :
if len (rest ) != 8 {
return ClientToRelayMsg {}, ErrInvalidFrame
}
var p [8 ]byte
copy (p [:], rest )
return ClientToRelayMsg {Type : ft , Ping : p }, nil
default :
return ClientToRelayMsg {}, fmt .Errorf ("%w: %s" , ErrUnknownFrameType , ft )
}
}
func parseDatagrams(b []byte , isBatch , noCopy bool ) (Datagrams , error ) {
if noCopy {
return datagramsFromBytesNoCopy (b , isBatch )
}
return datagramsFromBytes (b , isBatch )
}
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 .