protocol

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: MPL-2.0 Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const (
	CategoryUnknown   uint8 = 0
	CategorySovereign uint8 = 1 // RecType 0x01–0x0F
	CategoryTopic     uint8 = 2 // RecType 0x10–0x1F
	CategoryMailbox   uint8 = 3 // RecType 0x80–0x8F
)

DHT record categories (Phase 4 spec §2.1).

View Source
const (
	RecTypeTopic   uint8 = 0x10
	RecTypeMailbox uint8 = 0x80
)

RecType constants beyond endpoint (Phase 4).

View Source
const (
	// MaxMailboxPayloadCBOR is the spec §4.8 cap on SignedRecord.payload (MailboxPayload CBOR).
	MaxMailboxPayloadCBOR = 512
	// DefaultMailboxTTL is the recommended mailbox record TTL in seconds (spec §4.8).
	DefaultMailboxTTL uint32 = 3600
)
View Source
const (
	MailboxMsgConnectRequest uint8 = 0x01
	MailboxMsgCandidates     uint8 = 0x02
	MailboxMsgText           uint8 = 0x03
)

Mailbox message types (spec §4.3).

View Source
const (
	MsgPing          uint8 = 0x01
	MsgPong          uint8 = 0x02
	MsgFindNode      uint8 = 0x03
	MsgFindNodeResp  uint8 = 0x04
	MsgFindValue     uint8 = 0x05
	MsgFindValueResp uint8 = 0x06
	MsgStore         uint8 = 0x07
	MsgStoreResp     uint8 = 0x08
	MsgNATProbeReq   uint8 = 0x09 // NAT probe request: ask peer to echo to our claimed address
	MsgNATProbeEcho  uint8 = 0x0A // NAT probe echo: sent by responder directly to claimed address
)

Message type IDs (spec §7.4).

View Source
const (

	// RecTypeEndpoint is the Phase 1 endpoint advertisement (spec §7.6).
	RecTypeEndpoint uint8 = 0x01

	// MaxEndpointSignalURLLen caps each URL in EndpointPayload.Signal / Signals.
	MaxEndpointSignalURLLen = 2048
	// MaxSignalURLs caps EndpointPayload.Signals entry count (multi-center signal URLs).
	MaxSignalURLs = 8
	// MaxTurnURLs caps EndpointPayload.Turns entry count (credential-free relay hints for DHT).
	MaxTurnURLs = 16
	// MaxTurnURLEntryLen caps each turn:// string length in Turns.
	MaxTurnURLEntryLen = 512
)
View Source
const (
	NATUnknown uint8 = iota
	NATFullCone
	NATRestricted
	NATPortRestricted
	NATSymmetric
)

NAT types for EndpointPayload (spec §7.6).

View Source
const AgentProfilePayloadMaxSize = 700

AgentProfilePayloadMaxSize is the hard pre-publish rejection threshold (bytes). The FIND_VALUE response budget is ~1050 B; SignedRecord wrapper adds ~156 B, leaving ≈894 B for payload. We cap at 700 B to keep routing nodes comfortable.

View Source
const AgentProfilePayloadWarnSize = 350

AgentProfilePayloadWarnSize is a soft advisory threshold (bytes). Payloads approaching this size may compete with routing nodes for UDP packet space.

View Source
const MaxTopicBriefRunes = 140

MaxTopicBriefRunes is spec §5.3 one-line description limit.

View Source
const MaxTopicPayloadCBOR = 512

MaxTopicPayloadCBOR is the spec §5.9 cap on SignedRecord.payload for RecType 0x10.

View Source
const ProtocolVersion uint8 = 1

ProtocolVersion is the wire protocol version (spec §7.3).

View Source
const RecTypeAgentProfile uint8 = 0x02

RecTypeAgentProfile is the sovereign RecType for AgentProfilePayload (0x02).

Variables

View Source
var (

	// ErrInvalidMessage is returned for malformed wire data or failed verification.
	ErrInvalidMessage = errors.New("a2al/protocol: invalid message")
	// ErrUnknownMsgType is returned when msg_type is not defined in Phase 1.
	ErrUnknownMsgType = errors.New("a2al/protocol: unknown msg_type")
)
View Source
var (

	// ErrInvalidRecord is returned when structure, signature, or address binding fails.
	ErrInvalidRecord = errors.New("a2al/protocol: invalid record")
	// ErrRecordExpired means now is past Timestamp+TTL (VerifySignedRecord).
	ErrRecordExpired = errors.New("a2al/protocol: record expired")
	// ErrPayloadApproachingLimit is a non-fatal advisory: payload is valid but
	// approaching the soft size threshold and may compete with routing nodes.
	ErrPayloadApproachingLimit = errors.New("a2al/protocol: payload approaching size limit")
)

Functions

func EncodeMailboxPayload

func EncodeMailboxPayload(senderAddr, recipientAddr a2al.Address, recipientPub ed25519.PublicKey, msgType uint8, body []byte) ([]byte, error)

EncodeMailboxPayload encrypts (msgType, body) and returns canonical CBOR MailboxPayload.

func FindNodeResponseWireSize

func FindNodeResponseWireSize(resp *BodyFindNodeResp) (int, error)

FindNodeResponseWireSize returns the canonical CBOR size of a FIND_NODE_RESP body (UDP trim, spec §3.7).

func FindValueResponseWireSize

func FindValueResponseWireSize(resp *BodyFindValueResp) (int, error)

FindValueResponseWireSize returns the canonical CBOR size of a FIND_VALUE_RESP body (UDP trim, spec §3.7).

func FormatObservedUDP

func FormatObservedUDP(ip net.IP, port uint16) ([]byte, error)

FormatObservedUDP encodes IP:port to the same wire form as BodyPong.observed_addr. Both IPv4 (4+2 bytes = 6 total) and IPv6 (16+2 bytes = 18 total) are supported. No changes needed here for dual-stack.

func IsPublicIP added in v0.1.4

func IsPublicIP(ip net.IP) bool

IsPublicIP reports whether ip is a routeable public WAN address — not loopback, link-local, private, multicast, unspecified, or RFC 6598 CGNAT (100.64/10). Used by both dht and host packages to validate observed / claimed addresses.

Works for both IPv4 and IPv6. For IPv6: GUA (2000::/3) passes; ULA (fc00::/7) and link-local (fe80::/10) are rejected. No changes needed here for dual-stack.

func MarshalAgentProfilePayload added in v0.2.0

func MarshalAgentProfilePayload(p AgentProfilePayload) ([]byte, error)

MarshalAgentProfilePayload canonical-encodes p and enforces the size limit. Returns ErrPayloadApproachingLimit (non-fatal) wrapped in the error when the payload is within the hard limit but above the advisory threshold.

func MarshalSignedMessage

func MarshalSignedMessage(hdr Header, body any, priv ed25519.PrivateKey) ([]byte, error)

MarshalSignedMessage canonical-encodes header and body, signs, and returns the outer CBOR bytes.

func MarshalSignedMessageKeyStore

func MarshalSignedMessageKeyStore(hdr Header, body any, ks acrypto.KeyStore, addr a2al.Address) ([]byte, error)

MarshalSignedMessageKeyStore signs with KeyStore (spec: same preimage as MarshalSignedMessage).

func MarshalTopicPayload

func MarshalTopicPayload(p TopicPayload) ([]byte, error)

MarshalTopicPayload canonical-encodes p and enforces size / brief limits. If p.Version == 0, it is treated as 1 for encoding.

func ParseObservedUDP

func ParseObservedUDP(b []byte) (host string, port uint16, ok bool)

ParseObservedUDP decodes wire observed_addr (4+2 IPv4 or 16+2 IPv6, big-endian port). The wire format is address-family agnostic: 6 bytes = IPv4, 18 bytes = IPv6. Both IPv4 and IPv6 are fully supported; no changes needed here for dual-stack.

func RecordCategory

func RecordCategory(recType uint8) uint8

RecordCategory maps RecType to a storage/query policy bucket. Reserved ranges (0x20–0x7F, 0x90–0xFF) return CategoryUnknown and are stored like sovereign (key must be NodeID(Address)) for forward compatibility.

func RecordIsNewer

func RecordIsNewer(a, b SignedRecord) bool

RecordIsNewer reports whether a should replace b (spec: larger seq wins; tie-break by timestamp).

func TopicNodeID

func TopicNodeID(topic string) a2al.NodeID

TopicNodeID returns SHA-256("topic:" || topicUTF8) as the DHT key for Topic rendezvous (Phase 4 / v2 §10.3). The formula is immutable on the wire.

func VerifySignedRecord

func VerifySignedRecord(sr SignedRecord, now time.Time) error

VerifySignedRecord checks cryptographic integrity: signature validity, endpoint payload shape, and expiry. It does NOT enforce pubkey↔address authority (whether the signing key is allowed to publish for the given Address). Authority is a deployment policy; inject it via dht.Config.RecordAuth at the storage layer.

Types

type AgentProfilePayload added in v0.2.0

type AgentProfilePayload struct {
	Version    uint8          `cbor:"1,keyasint"`
	Name       string         `cbor:"2,keyasint,omitempty"`
	Brief      string         `cbor:"3,keyasint,omitempty"`
	Protocols  []string       `cbor:"4,keyasint,omitempty"`
	Skills     []string       `cbor:"5,keyasint,omitempty"`
	CardHash   []byte         `cbor:"6,keyasint,omitempty"`
	Modalities []string       `cbor:"8,keyasint,omitempty"`
	Meta       map[string]any `cbor:"9,keyasint,omitempty"`
}

AgentProfilePayload is the CBOR body of a RecType=0x02 sovereign record. All fields except Version are optional (omitempty). Integer map keys minimise wire footprint under the 700 B cap.

func ParseAgentProfilePayload added in v0.2.0

func ParseAgentProfilePayload(sr SignedRecord) (AgentProfilePayload, error)

ParseAgentProfilePayload decodes the payload from a RecType=0x02 SignedRecord.

type BodyFindNode

type BodyFindNode struct {
	Target []byte `cbor:"1,keyasint"` // 32 NodeID
}

type BodyFindNodeResp

type BodyFindNodeResp struct {
	Nodes        []NodeInfo `cbor:"1,keyasint"`
	ObservedAddr []byte     `cbor:"2,keyasint"`
}

type BodyFindValue

type BodyFindValue struct {
	Target  []byte `cbor:"1,keyasint"`
	RecType uint8  `cbor:"2,keyasint,omitempty"` // 0 = all types
}

type BodyFindValueResp

type BodyFindValueResp struct {
	Nodes        []NodeInfo     `cbor:"1,keyasint"`
	Record       *SignedRecord  `cbor:"2,keyasint,omitempty"`
	ObservedAddr []byte         `cbor:"3,keyasint"`
	Records      []SignedRecord `cbor:"4,keyasint,omitempty"`
}

type BodyNATProbeEcho added in v0.1.4

type BodyNATProbeEcho struct {
	Token []byte `cbor:"1,keyasint"` // 8-byte nonce echoed from BodyNATProbeReq
}

BodyNATProbeEcho is sent by the responder directly to the requester's claimed address. Token echoes the nonce from the corresponding BodyNATProbeReq.

type BodyNATProbeReq added in v0.1.4

type BodyNATProbeReq struct {
	Token       []byte `cbor:"1,keyasint"` // 8-byte random nonce
	ClaimedAddr []byte `cbor:"2,keyasint"` // wire-encoded UDP address (6 or 18 bytes)
}

BodyNATProbeReq asks the receiver to send a NATProbeEcho directly to ClaimedAddr. Token (8 bytes) correlates the echo back to the requester's wait channel.

type BodyPing

type BodyPing struct {
	Address []byte `cbor:"1,keyasint"` // 21
}

type BodyPong

type BodyPong struct {
	Address      []byte `cbor:"1,keyasint"`
	ObservedAddr []byte `cbor:"2,keyasint"` // 6 or 18
}

type BodyStore

type BodyStore struct {
	Record SignedRecord `cbor:"1,keyasint"`
	Key    []byte       `cbor:"2,keyasint,omitempty"` // 32-byte DHT key; omit = NodeID(Address)
}

type BodyStoreResp

type BodyStoreResp struct {
	Stored     bool        `cbor:"1,keyasint"`
	AlreadyHad bool        `cbor:"2,keyasint,omitempty"` // true when the node already held an equal-or-newer record
	Reason     StoreReason `cbor:"3,keyasint,omitempty"` // non-zero only on rejection; 0 from old nodes means unknown
}

type DecodedMessage

type DecodedMessage struct {
	Header       Header
	SenderPubkey ed25519.PublicKey
	SenderAddr   a2al.Address
	Body         any
}

DecodedMessage is a verified, parsed wire message.

func VerifyAndDecode

func VerifyAndDecode(raw []byte) (*DecodedMessage, error)

VerifyAndDecode parses the outer message, re-canonicalizes header/body, verifies Ed25519, checks sender_pubkey matches body.address for PING and PONG (spec §Step 3).

type DiscoverFilter

type DiscoverFilter struct {
	Protocols []string `json:"protocols,omitempty"`
	Tags      []string `json:"tags,omitempty"`
}

DiscoverFilter is optional client-side filtering (spec §5.5).

type EndpointPayload

type EndpointPayload struct {
	Endpoints []string `cbor:"1,keyasint"`
	NatType   uint8    `cbor:"2,keyasint"`
	// Signal is the primary WebSocket base URL for ICE trickle signaling (no query; room is
	// appended by peers). Kept for backward compatibility with nodes that only read key 3.
	Signal string `cbor:"3,keyasint,omitempty"`
	// Turns lists optional turn:// URLs without credentials (public relay hints).
	Turns []string `cbor:"4,keyasint,omitempty"`
	// Signals lists additional WebSocket base URLs for ICE signaling (multi-center).
	// New nodes prefer this list; old nodes that only read Signal (key 3) still work.
	// When non-empty, Signal should equal Signals[0] for maximum compatibility.
	Signals []string `cbor:"5,keyasint,omitempty"`
}

EndpointPayload is the CBOR inside SignedRecord.payload for rec_type=0x01 (spec §7.6).

type EndpointRecord

type EndpointRecord struct {
	Address   a2al.Address
	Endpoints []string
	NatType   uint8
	Signal    string
	Turns     []string
	// Signals is the multi-center signal URL list from EndpointPayload.Signals (key 5).
	// When non-empty, callers should prefer this over Signal. Signal is kept for compat.
	Signals   []string
	Timestamp uint64
	Seq       uint64
	TTL       uint32
}

EndpointRecord is the decoded logical view (spec Step 4); no signature material.

func ParseEndpointRecord

func ParseEndpointRecord(sr SignedRecord) (EndpointRecord, error)

ParseEndpointRecord decodes an endpoint record after verification.

type Header struct {
	Version  uint8  `cbor:"1,keyasint"`
	Features uint16 `cbor:"2,keyasint"`
	MsgType  uint8  `cbor:"3,keyasint"`
	TxID     []byte `cbor:"4,keyasint"`
}

Header is the CBOR map in field 1 of the outer message (spec §7.3).

type MailboxMessage

type MailboxMessage struct {
	Sender  a2al.Address
	MsgType uint8
	Body    []byte
	// Seq is copied from the outer SignedRecord.Seq; it is monotonically
	// increasing per sender and serves as a unique record identifier.
	Seq uint64
	// SenderPubkey is the sender's Ed25519 identity public key, taken from
	// SignedRecord.Pubkey after signature verification.  It is guaranteed to
	// correspond to Sender (same key that derived the AID).  Callers may use
	// this to encrypt a reply without an extra DHT lookup.
	SenderPubkey ed25519.PublicKey
}

MailboxMessage is one decrypted mailbox entry for the application (spec §4.6).

func OpenMailboxRecord

func OpenMailboxRecord(recipientPriv ed25519.PrivateKey, recipientAddr a2al.Address, sr SignedRecord) (MailboxMessage, error)

OpenMailboxRecord decrypts a verified mailbox SignedRecord for recipientAddr.

type MailboxPayload

type MailboxPayload struct {
	Recipient   []byte `cbor:"1,keyasint"`
	SenderAddr  []byte `cbor:"2,keyasint"`
	EphemeralPK []byte `cbor:"3,keyasint"`
	Ciphertext  []byte `cbor:"4,keyasint"`
	Nonce       []byte `cbor:"5,keyasint"`
}

MailboxPayload is the CBOR inside SignedRecord.payload for rec_type=0x80 (spec §4.1).

type NodeInfo

type NodeInfo struct {
	Address []byte `cbor:"1,keyasint"` // 21 bytes
	NodeID  []byte `cbor:"2,keyasint"` // 32 bytes
	IP      []byte `cbor:"3,keyasint"` // 4 or 16 bytes
	Port    uint16 `cbor:"4,keyasint"`
}

NodeInfo is the DHT routing contact shape (spec §7.6).

type SignedRecord

type SignedRecord struct {
	Address    []byte `cbor:"1,keyasint"`
	RecType    uint8  `cbor:"2,keyasint"`
	Payload    []byte `cbor:"3,keyasint"`
	Seq        uint64 `cbor:"4,keyasint"`
	Timestamp  uint64 `cbor:"5,keyasint"`
	TTL        uint32 `cbor:"6,keyasint"`
	Pubkey     []byte `cbor:"7,keyasint"`
	Signature  []byte `cbor:"8,keyasint"`
	Delegation []byte `cbor:"9,keyasint,omitempty"`
}

SignedRecord is the on-wire record container (spec §7.6). Record signing uses prefix "a2al-rec\0" over CBOR of fields 1–6 (implemented in record.go, Step 4). Field 9 (Delegation) is optional and carries a CBOR-encoded DelegationProof when the signing key (Pubkey) is an operational key publishing on behalf of a master-derived Address.

func SignEndpointRecord

func SignEndpointRecord(priv ed25519.PrivateKey, addr a2al.Address, ep EndpointPayload, seq, timestamp uint64, ttl uint32) (SignedRecord, error)

SignEndpointRecord builds a SignedRecord for a self-signed endpoint advertisement. The signing key must derive the same Address as addr (Phase 1/2 path).

func SignEndpointRecordDelegated

func SignEndpointRecordDelegated(opPriv ed25519.PrivateKey, delegationCBOR []byte, addr a2al.Address, ep EndpointPayload, seq, timestamp uint64, ttl uint32) (SignedRecord, error)

SignEndpointRecordDelegated builds a SignedRecord where an operational key signs on behalf of a master-derived AID (Phase 3 delegation path). delegationCBOR is embedded verbatim in the record and will be verified by receivers via the RecordAuth policy injected into the DHT store. Callers must ensure delegationCBOR is a valid DelegationProof for addr authorized to opPriv.

func SignRecord

func SignRecord(priv ed25519.PrivateKey, addr a2al.Address, recType uint8, payload []byte, seq, timestamp uint64, ttl uint32) (SignedRecord, error)

SignRecord builds a signed record for RecType sovereign custom (0x02–0x0F), topic (0x10–0x1F), or mailbox (0x80–0x8F). payload must be CBOR-encoded bytes.

func SignRecordDelegated

func SignRecordDelegated(opPriv ed25519.PrivateKey, delegationCBOR []byte, addr a2al.Address, recType uint8, payload []byte, seq, timestamp uint64, ttl uint32) (SignedRecord, error)

SignRecordDelegated is SignRecord with an operational key and DelegationProof.

type StoreReason added in v0.1.8

type StoreReason uint8

StoreReason is the machine-readable cause carried in BodyStoreResp.Reason. Zero (StoreReasonOK) is the default and means no error information is present, which is also what older nodes send. New reasons must be assigned new non-zero values; existing values must never be reused for a different meaning.

const (
	StoreReasonOK            StoreReason = 0 // stored or already-had (see Stored/AlreadyHad)
	StoreReasonPolicy        StoreReason = 1 // node-side policy rejected (ACL, whitelist, etc.) — stable refusal
	StoreReasonRecordInvalid StoreReason = 2 // record-level rejection (bad sig, key mismatch, delegation error)
)

type TopicEntry

type TopicEntry struct {
	Address   a2al.Address
	Seq       uint64
	Timestamp uint64
	TTL       uint32
	Version   uint8
	Topic     string
	Name      string
	Protocols []string
	Tags      []string
	Brief     string
	Meta      map[string]any
}

TopicEntry is the decoded logical view for discovery (spec §5.7), like EndpointRecord.

func FilterTopicEntries

func FilterTopicEntries(in []TopicEntry, f *DiscoverFilter) []TopicEntry

FilterTopicEntries applies protocol/tag filters (AND: all filter tags must appear in entry.Tags; at least one protocol match if filter protocols non-empty).

func TopicEntryFromSignedRecord

func TopicEntryFromSignedRecord(sr SignedRecord) (TopicEntry, error)

TopicEntryFromSignedRecord builds TopicEntry after caller verifies signature / expiry.

type TopicPayload

type TopicPayload struct {
	Version   uint8          `cbor:"1,keyasint"`
	Topic     string         `cbor:"2,keyasint"`
	Name      string         `cbor:"3,keyasint"`
	Protocols []string       `cbor:"4,keyasint"`
	Tags      []string       `cbor:"5,keyasint"`
	Brief     string         `cbor:"6,keyasint"`
	Meta      map[string]any `cbor:"7,keyasint,omitempty"`
}

TopicPayload is the CBOR inside SignedRecord.payload for rec_type=0x10 (spec §5.3).

func ParseTopicRecord

func ParseTopicRecord(sr SignedRecord) (TopicPayload, error)

ParseTopicRecord decodes a topic SignedRecord payload.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL