FIPS Wire Formats

May 8, 2026 · View on GitHub

This document is the comprehensive wire format reference for all three protocol layers. It covers transport framing, link-layer message formats, and session-layer message formats, with an encapsulation walkthrough showing how application data is wrapped through each layer.

FMP Message Type Catalog

The FMP link layer defines the following message types, dispatched by the msg_type byte in the encrypted inner header:

TypeNameForwarding
0x00SessionDatagramRouted hop-by-hop toward the destination
0x01SenderReportPeer-to-peer (MMP, link-layer instance)
0x02ReceiverReportPeer-to-peer (MMP, link-layer instance)
0x10TreeAnnouncePeer-to-peer (spanning-tree gossip)
0x20FilterAnnouncePeer-to-peer (bloom-filter gossip)
0x30LookupRequestForwarded — bloom-guided through tree peers
0x31LookupResponseForwarded — reverse-path via recent_requests
0x50DisconnectPeer-to-peer (orderly link teardown)
0x51HeartbeatPeer-to-peer (link liveness)

Handshake messages travel before encryption is established and are identified by the FMP common-prefix phase field rather than a msg_type byte (phase 0x1 = Noise IK msg1, phase 0x2 = Noise IK msg2).

Packet Type Summary

A higher-level summary that includes typical sizes and forwarding category:

MessageTypical SizeWhenForwarded?
TreeAnnounceVariable (depth-dependent)Topology changesNo (peer-to-peer)
FilterAnnounce~1 KBTopology changesNo (peer-to-peer)
LookupRequest~300 bytesFirst contact, recoveryYes (bloom-guided tree)
LookupResponse~400 bytesResponse to discoveryYes (reverse-path)
SessionDatagram + SessionSetup~232–402 bytesSession establishmentYes (routed)
SessionDatagram + SessionAck~170 bytesSession confirmationYes (routed)
SessionDatagram + Data (minimal)77 bytes + IPv6 payloadBulk IPv6 traffic (compressed)Yes (routed)
SessionDatagram + Data (with CP)77 + coords + IPv6 payloadWarmup/recovery (compressed)Yes (routed)
SessionDatagram + CoordsRequired70 bytesCache miss errorYes (routed)
SessionDatagram + PathBroken70+ bytesDead-end errorYes (routed)
Disconnect2 bytesLink teardownNo (peer-to-peer)

Encoding Rules

  • All multi-byte integers are little-endian (LE)
  • NodeAddr is 16 bytes — truncated SHA-256 hash of public key
  • Signatures are 64 bytes — secp256k1 Schnorr
  • Variable-length arrays use a 2-byte u16 LE count prefix followed by that many items
  • Public keys are 33 bytes — compressed secp256k1 (02/03 prefix + 32 bytes)

Transport Framing

Datagram-oriented transports (UDP, raw Ethernet, radio) preserve natural packet boundaries and require no additional framing. Stream-oriented transports (TCP, Tor) must delineate FIPS packets within the byte stream; the common prefix payload_len field provides this framing directly. TCP and Tor share a common stream reader (tcp/stream.rs) that implements this framing.

Ethernet data frame header. The Ethernet transport prepends a 3-byte header before the FMP payload on data frames: a 1-byte frame type (0x00) followed by a 2-byte little-endian payload length. The length field allows the receiver to trim Ethernet minimum-frame padding that would otherwise corrupt AEAD verification. Beacon frames (0x01) have no length field (fixed 34-byte payload). These bytes are consumed by the transport layer and are not visible to FMP. The effective MTU for FMP is the interface MTU minus three bytes (typically 1497).

All FMP packets begin with a 4-byte common prefix that identifies the protocol version, session lifecycle phase, per-packet flags, and payload length.

Common Prefix (4 bytes)

Common Prefix

FieldSizeDescription
version4 bits (high)Protocol version. Currently 0x0
phase4 bits (low)Session lifecycle phase (see table)
flags1 bytePer-packet signal flags (zero during handshake)
payload_len2 bytes LELength of payload after phase-specific header, excluding AEAD tag

Phase Table

PhaseTypeDescription
0x0Established framePost-handshake encrypted traffic
0x1Noise IK msg1Handshake initiation
0x2Noise IK msg2Handshake response

Flags (Established Phase Only)

BitNameDescription
0K (key epoch)Selects active key during rekeying
1CECongestion Experienced echo
2SP (spin bit)RTT measurement
3-7Reserved (must be zero)

Flags must be zero in handshake packets (phase 0x1 and 0x2).

Established Frame (phase 0x0)

All post-handshake traffic between authenticated peers. Contains one encrypted link-layer message.

Outer header (16 bytes, used as AEAD AAD):

Established frame outer header

FieldSizeDescription
common prefix4 bytesver=0, phase=0, flags, payload_len
receiver_idx4 bytes LESession index for O(1) lookup
counter8 bytes LEMonotonic nonce, used as AEAD nonce and for replay detection

The entire 16-byte header is authenticated as Associated Data (AAD) in the ChaCha20-Poly1305 AEAD construction.

Encrypted inner header (5 bytes, first bytes of plaintext):

Established frame inner header

FieldSizeDescription
timestamp4 bytes LESession-relative milliseconds (u32)
msg_type1 byteLink-layer message type

After decryption, the plaintext begins with the 4-byte timestamp followed by the 1-byte message type and message-specific fields.

Complete encrypted frame:

Complete encrypted frame

Message Type Table

TypeMessageDescription
0x00SessionDatagramEncapsulated session-layer payload for forwarding
0x01SenderReportMMP sender-side metrics report (48 bytes)
0x02ReceiverReportMMP receiver-side metrics report (68 bytes)
0x10TreeAnnounceSpanning tree state announcement
0x20FilterAnnounceBloom filter reachability update
0x30LookupRequestCoordinate discovery request
0x31LookupResponseCoordinate discovery response
0x50DisconnectOrderly link teardown
0x51HeartbeatLink liveness probe

Noise IK Message 1 (phase 0x1)

Handshake initiation from connecting party.

Noise IK message 1

Common prefix: ver=0, phase=0x1, flags=0, payload_len=110 (4 + 106).

FieldSizeDescription
common prefix4 bytesver=0, phase=1, flags=0, payload_len
sender_idx4 bytes LEInitiator's session index (becomes receiver's receiver_idx)
noise_msg1106 bytesNoise IK first message

Noise msg1 breakdown (106 bytes):

OffsetFieldSizeDescription
0ephemeral_pubkey33 bytesInitiator's ephemeral key (compressed secp256k1)
33encrypted_static49 bytesInitiator's static key (33) + AEAD tag (16)
82encrypted_epoch24 bytesStartup epoch (8) + AEAD tag (16)

Noise pattern: -> e, es, s, ss with epoch payload

Noise IK Message 2 (phase 0x2)

Handshake response from responder.

Noise IK message 2

Common prefix: ver=0, phase=0x2, flags=0, payload_len=65 (4 + 4 + 57).

FieldSizeDescription
common prefix4 bytesver=0, phase=2, flags=0, payload_len
sender_idx4 bytes LEResponder's session index
receiver_idx4 bytes LEEcho of initiator's sender_idx from msg1
noise_msg257 bytesNoise IK second message

Noise msg2 breakdown (57 bytes):

OffsetFieldSizeDescription
0ephemeral_pubkey33 bytesResponder's ephemeral key (compressed secp256k1)
33encrypted_epoch24 bytesStartup epoch (8) + AEAD tag (16)

Noise pattern: <- e, ee, se with epoch payload

After msg2, both parties derive identical symmetric session keys. The encrypted epoch in msg1 and msg2 enables peer restart detection — if a peer's epoch changes, the other side knows it restarted and must re-establish the link.

Index Semantics

Each party in a link session maintains two indices:

IndexChosen ByUsed ByPurpose
our_indexUsThemThey include this as receiver_idx in packets to us
their_indexThemUsWe include this as receiver_idx in packets to them

Handshake Flow

Handshake flow

These messages are carried as plaintext inside encrypted frames (phase 0x0). After decryption of the AEAD ciphertext, the plaintext begins with a 4-byte session-relative timestamp followed by the 1-byte message type and message-specific fields.

TreeAnnounce (0x10)

Spanning tree state announcement, exchanged between direct peers only.

TreeAnnounce

OffsetFieldSizeDescription
0msg_type1 byte0x10
1version1 byte0x01 (v1)
2sequence8 bytes LEMonotonic counter, increments on parent change
10timestamp8 bytes LEUnix seconds
18parent16 bytesNodeAddr of selected parent (self = root)
34ancestry_count2 bytes LENumber of AncestryEntry records
36ancestry32 x n bytesAncestryEntry array (self -> root)
36 + 32nsignature64 bytesSchnorr signature over entire message

AncestryEntry (32 bytes):

AncestryEntry

OffsetFieldSizeDescription
0node_addr16 bytesNode's routing identifier
16sequence8 bytes LENode's sequence number
24timestamp8 bytes LENode's Unix timestamp

Size: 100 + (n x 32) bytes, where n = ancestry_count (depth + 1, includes self)

Tree DepthPayloadWith Link Overhead
0 (root)132 bytes169 bytes
3228 bytes265 bytes
5292 bytes329 bytes
10452 bytes489 bytes

FilterAnnounce (0x20)

Bloom filter reachability update, exchanged between direct peers only.

FilterAnnounce

OffsetFieldSizeDescription
0msg_type1 byte0x20
1sequence8 bytes LEMonotonic counter for freshness
9hash_count1 byteNumber of hash functions (5 in v1)
10size_class1 byteFilter size: 512 << size_class bytes
11filter_bitsvariableBloom filter bit array

Size class table:

size_classBytesBitsStatus
05124,096Reserved
11,0248,192v1 (MUST use)
22,04816,384Reserved
34,09632,768Reserved

v1 payload: 1,035 bytes (11 header + 1,024 filter). With link overhead: 1,072 bytes.

LookupRequest (0x30)

Coordinate discovery request, routed through the spanning tree via bloom-filter-guided forwarding. Each transit node forwards only to tree peers (parent + children) whose bloom filter contains the target. Request_id dedup in recent_requests handles edge cases from tree restructuring.

LookupRequest

OffsetFieldSizeDescription
0msg_type1 byte0x30
1request_id8 bytes LEUnique random identifier
9target16 bytesNodeAddr being sought
25origin16 bytesRequester's NodeAddr
41ttl1 byteRemaining hops (default 64)
42min_mtu2 bytes LEMinimum transport MTU the origin requires (0 = no requirement)
44origin_coords_cnt2 bytes LENumber of coordinate entries
46origin_coords16 x n bytesRequester's ancestry (NodeAddr only)

Size: 46 + (n x 16) bytes, where n = origin depth + 1

Origin DepthPayload
3110 bytes
5142 bytes
10222 bytes

LookupResponse (0x31)

Coordinate discovery response, reverse-path routed back to the requester via the transit nodes that forwarded the request.

LookupResponse

OffsetFieldSizeDescription
0msg_type1 byte0x31
1request_id8 bytes LEEchoes the request's ID
9target16 bytesNodeAddr that was found
25path_mtu2 bytes LEMinimum MTU along response path (transit-annotated)
27target_coords_cnt2 bytes LENumber of coordinate entries
29target_coords16 x n bytesTarget's ancestry (NodeAddr only)
29 + 16nproof64 bytesSchnorr signature over (request_id || target || target_coords)

Size: 93 + (n x 16) bytes

Target DepthPayload
3141 bytes
5173 bytes
10253 bytes

The path_mtu field is initialized to u16::MAX by the target and each transit hop applies min(path_mtu, outgoing_link_mtu), giving the originator an MTU estimate for the discovered path.

Proof coverage: Signs (request_id || target || target_coords)path_mtu is excluded from the proof because it is a transit annotation modified at each hop. Coordinates are included because proof verification at the source confirms the target actually holds the claimed tree position. The source verifies the proof upon receipt.

SessionDatagram (0x00)

Encapsulated session-layer payload for multi-hop forwarding.

SessionDatagram

OffsetFieldSizeDescription
0msg_type1 byte0x00
1ttl1 byteRemaining hops, decremented each hop
2path_mtu2 bytes LEPath MTU, min'd at each forwarding hop
4src_addr16 bytesSource NodeAddr
20dest_addr16 bytesDestination NodeAddr
36payloadvariableSession-layer message

Fixed header: 36 bytes (SESSION_DATAGRAM_HEADER_SIZE)

The path_mtu field is initialized to u16::MAX by the sender and each forwarding hop applies min(path_mtu, outgoing_link_mtu), giving the receiver an estimate of the minimum MTU along the path.

The payload is opaque to transit nodes — session-layer encrypted independently of link encryption.

Disconnect (0x50)

Orderly link teardown with reason code.

Disconnect

OffsetFieldSizeDescription
0msg_type1 byte0x50
1reason1 byteDisconnect reason code

Reason codes:

CodeNameDescription
0x00ShutdownNormal operator-requested stop
0x01RestartRestarting, may reconnect soon
0x02ProtocolErrorProtocol error encountered
0x03TransportFailureTransport failure
0x04ResourceExhaustionMemory or connection limit
0x05SecurityViolationAuthentication or policy violation
0x06ConfigurationChangePeer removed from configuration
0x07TimeoutHeartbeat liveness timeout
0xFFOtherUnspecified reason

SenderReport (0x01)

Sent by the frame sender to provide interval-based transmission statistics.

SenderReport

OffsetFieldSizeEncoding
0msg_type10x01
1reserved3Zero
4interval_start_counter8u64 LE — first counter in this interval
12interval_end_counter8u64 LE — last counter in this interval
20interval_start_timestamp4u32 LE — timestamp at interval start
24interval_end_timestamp4u32 LE — timestamp at interval end
28interval_bytes_sent4u32 LE — payload bytes sent in interval
32cumulative_packets_sent8u64 LE — total packets sent on this link
40cumulative_bytes_sent8u64 LE — total bytes sent on this link

Total: 48 bytes.

ReceiverReport (0x02)

Sent by the frame receiver to provide loss, jitter, and timing feedback.

ReceiverReport

OffsetFieldSizeEncoding
0msg_type10x02
1reserved3Zero
4highest_counter8u64 LE — highest counter value received
12cumulative_packets_recv8u64 LE — total packets received
20cumulative_bytes_recv8u64 LE — total bytes received
28timestamp_echo4u32 LE — echoed sender timestamp for RTT
32dwell_time2u16 LE — time between receive and echo (ms)
34max_burst_loss2u16 LE — largest loss burst in interval
36mean_burst_loss2u16 LE — mean burst length (u8.8 fixed-point)
38reserved2Zero
40jitter4u32 LE — interarrival jitter (microseconds)
44ecn_ce_count4u32 LE — cumulative ECN-CE marked packets
48owd_trend4i32 LE — one-way delay trend (µs/s, signed)
52burst_loss_count4u32 LE — number of loss bursts in interval
56cumulative_reorder_count4u32 LE — total reordered packets
60interval_packets_recv4u32 LE — packets received in interval
64interval_bytes_recv4u32 LE — bytes received in interval

Total: 68 bytes.

Session-Layer Message Formats

Session-layer messages are carried as the payload of a SessionDatagram (0x00). All FSP messages begin with a 4-byte common prefix that identifies the protocol version, session lifecycle phase, per-packet flags, and payload length.

FSP Common Prefix (4 bytes)

FieldSizeDescription
version4 bits (high)Protocol version. Currently 0x0
phase4 bits (low)Session lifecycle phase (see table)
flags1 bytePer-packet signal flags (zero during handshake)
payload_len2 bytes LELength of payload after phase-specific header

FSP Phase Table

PhaseTypeDescription
0x0EstablishedPost-handshake encrypted traffic or plaintext error signals
0x1Handshake msg1SessionSetup (Noise XK msg1)
0x2Handshake msg2SessionAck (Noise XK msg2)
0x3Handshake msg3SessionMsg3 (Noise XK msg3)

FSP Flags (Established Phase Only)

BitNameDescription
0CP (coords present)Source and destination coordinates follow the header in cleartext
1K (key epoch)Selects active key during rekeying
2U (unencrypted)Payload is plaintext (error signals)
3-7Reserved (must be zero)

Flags must be zero in handshake packets (phase 0x1, 0x2, and 0x3).

FSP Encrypted Message (phase 0x0, U flag clear)

Post-handshake encrypted data. The 12-byte cleartext header is used as AEAD AAD. Coordinates may appear in cleartext between the header and ciphertext when the CP flag is set.

Cleartext header (12 bytes, used as AEAD AAD):

FieldSizeDescription
common prefix4 bytesver=0, phase=0, flags, payload_len
counter8 bytes LEMonotonic nonce, used as AEAD nonce and for replay detection

Optional cleartext coordinates (when CP flag is set):

FieldSizeDescription
src_coords_count2 bytes LENumber of source coordinate entries
src_coords16 x n bytesSource's ancestry (NodeAddr, self -> root)
dest_coords_count2 bytes LENumber of dest coordinate entries
dest_coords16 x m bytesDestination's ancestry

Transit nodes parse the CP flag and extract coordinates without decryption.

Encrypted inner header (6 bytes, first bytes of AEAD plaintext):

FieldSizeDescription
timestamp4 bytes LESession-relative milliseconds (u32)
msg_type1 byteSession-layer message type
inner_flags1 byteBit 0: SP (spin bit for RTT measurement)

After the inner header, the remaining plaintext is the message-type-specific body.

Complete encrypted message:

FSP complete encrypted message

FSP Session Message Types

TypeMessageDescription
0x10DataPort-multiplexed service payload (see DataPacket below)
0x11SenderReportMMP sender-side metrics report
0x12ReceiverReportMMP receiver-side metrics report
0x13PathMtuNotificationEnd-to-end path MTU echo
0x14CoordsWarmupStandalone coordinate cache warming
0x20CoordsRequiredError: transit node lacks destination coordinates
0x21PathBrokenError: greedy routing reached dead end
0x22MtuExceededError: forwarded packet exceeds next-hop MTU

Message types 0x10-0x14 are carried inside the AEAD ciphertext (dispatched by the msg_type field in the encrypted inner header). Types 0x20-0x22 are plaintext error signals (U flag set, no encryption).

Session-layer SenderReport (0x11) and ReceiverReport (0x12) carry the same metric fields as their link-layer counterparts (0x01 and 0x02), but the body framing differs because the FSP encrypted inner header already carries the message-type byte. The session body therefore omits the msg_type byte and uses 2 reserved bytes (not 3) before the fields:

LayerWire sizeHeader inside body
Link SenderReport (0x01)48 bytesmsg_type(1) + reserved(3) + fields(44)
Session SenderReport (0x11)46 bytesreserved(2) + fields(44)
Link ReceiverReport (0x02)68 bytesmsg_type(1) + reserved(3) + fields(64)
Session ReceiverReport (0x12)66 bytesreserved(2) + fields(64)

Dispatch happens at the session level via the msg_type byte in the FSP encrypted inner header.

SessionSetup (phase 0x1)

Establishes a session and warms transit coordinate caches. Contains the first message of the Noise XK handshake (ephemeral key only — the initiator's static identity is not revealed until msg3).

SessionSetup, SessionAck, and SessionMsg3 are identified by the phase field in the FSP common prefix (0x1, 0x2, 0x3), not by a message type byte. The msg_type field in the encrypted inner header applies only to established-phase (0x0) messages.

Encoded with FSP prefix: ver=0, phase=0x1, flags=0, payload_len.

SessionSetup

Body (after 4-byte FSP prefix):

OffsetFieldSizeDescription
0flags1 byteBit 0: REQUEST_ACK, Bit 1: BIDIRECTIONAL
1src_coords_count2 bytes LENumber of source coordinate entries
3src_coords16 x n bytesSource's ancestry (NodeAddr, self -> root)
...dest_coords_count2 bytes LENumber of dest coordinate entries
...dest_coords16 x m bytesDestination's ancestry
...handshake_len2 bytes LENoise payload length
...handshake_payloadvariableNoise XK msg1 (33 bytes — ephemeral key only)

SessionAck (phase 0x2)

Second message of the Noise XK handshake. The responder sends its ephemeral key and encrypted epoch. Encoded with FSP prefix: ver=0, phase=0x2, flags=0, payload_len.

SessionAck

Body (after 4-byte FSP prefix):

OffsetFieldSizeDescription
0flags1 byteReserved
1src_coords_count2 bytes LENumber of acknowledger coordinate entries
3src_coords16 x n bytesAcknowledger's ancestry (for cache warming)
...dest_coords_count2 bytes LENumber of initiator coordinate entries
...dest_coords16 x m bytesInitiator's ancestry (for return-path cache warming)
...handshake_len2 bytes LENoise payload length
...handshake_payloadvariableNoise XK msg2 (57 bytes — ephemeral key + encrypted epoch)

SessionMsg3 (phase 0x3)

Third and final message of the Noise XK handshake. The initiator reveals its encrypted static identity and epoch. After msg3, both parties derive identical symmetric session keys and the session is established. Encoded with FSP prefix: ver=0, phase=0x3, flags=0, payload_len.

SessionMsg3

Body (after 4-byte FSP prefix):

OffsetFieldSizeDescription
0flags1 byteReserved
1handshake_len2 bytes LENoise payload length
3handshake_payloadvariableNoise XK msg3 (73 bytes — encrypted static + encrypted epoch)

Noise XK msg3 breakdown (73 bytes):

OffsetFieldSizeDescription
0encrypted_static49 bytesInitiator's static key (33) + AEAD tag (16)
49encrypted_epoch24 bytesStartup epoch (8) + AEAD tag (16)

SessionMsg3 does not carry coordinates — both endpoints already have each other's coordinates from SessionSetup (msg1) and SessionAck (msg2).

Data (0x10) — DataPacket Port Multiplexing

DataPacket is the primary application data carrier. The body after the 6-byte encrypted inner header contains a 4-byte port header followed by the service payload:

OffsetFieldSizeDescription
0src_port2 bytes LESource service port
2dst_port2 bytes LEDestination service port
4payloadvariableService-specific payload

The receiver dispatches by dst_port to the registered service handler.

Port registry (three tiers):

RangePurpose
0–255 (0x00–0xFF)Reserved, protocol use
256–1023 (0x100–0x3FF)Reserved, FIPS standard services
1024–65535 (0x400–0xFFFF)Application use

Initial assignment: Port 256 (0x100) = IPv6 shim.

IPv6 Shim Payload Format (Port 256)

The IPv6 shim defines its own payload format with a leading format byte:

OffsetFieldSizeDescription
0format1 byteCompression format (0x00 = mesh-internal compressed)
1fieldsvariableFormat-specific residual fields

Format 0x00 — mesh-internal compressed (default):

Strips source and destination IPv6 addresses (32 bytes) and payload length (2 bytes) from each packet. Carries residual fields that cannot be derived from session context:

OffsetFieldSizeDescription
0format1 byte0x00
1traffic_class1 byteIPv6 Traffic Class (DSCP + ECN)
2flow_label3 bytesIPv6 Flow Label (20 bits, big-endian, zero-padded)
5next_header1 byteIPv6 Next Header (protocol identifier)
6hop_limit1 byteIPv6 Hop Limit
7upper_payloadvariableUpper-layer payload (TCP, UDP, ICMPv6, etc.)

The receiver reconstructs the full 40-byte IPv6 header from session context (source and destination addresses derived from session npubs, version = 6, payload length from outer packet length) plus the 6 bytes of residual fields, then delivers the complete IPv6 packet to the TUN interface.

Format 0x01+: Reserved for future use (e.g., full-header gateway traffic).

Compression savings: 29 bytes per packet (34 bytes stripped, 7 bytes format + residual added). Net overhead for IPv6 traffic: 77 bytes (FIPS_IPV6_OVERHEAD), down from 110 bytes base DataPacket overhead.

PathMtuNotification (0x13)

Sent by the destination to report the observed forward-path MTU.

PathMtuNotification

OffsetFieldSizeEncoding
0path_mtu2u16 LE — minimum MTU observed along the forward path

Total body: 2 bytes (plus FSP common prefix + encrypted header + AEAD tag).

CoordsWarmup (0x14)

Standalone coordinate cache warming message. Sent when piggybacking coordinates via the CP flag on a data packet would exceed the transport MTU, or as an immediate response to CoordsRequired/PathBroken signals (rate-limited).

CoordsWarmup is an encrypted FSP message with the CP flag set and an empty body. Transit nodes extract coordinates via the existing CP-flag parsing path — no transit-side changes required.

Wire format:

FSP header (12 bytes, AAD): ver=0, phase=0, flags=CP, counter, payload_len
Cleartext coords: src_coords + dst_coords (same encoding as CP flag)
AEAD ciphertext: inner_header(6) + Poly1305 tag(16) = 22 bytes

Total FSP payload: 12 + coords + 22

The cleartext coords section uses the same variable-length encoding as any CP-flagged message: src_coords_count(2) + src_coords(16×n) + dest_coords_count(2) + dest_coords(16×m).

Typical size (depth-3 tree): 12 + (2+64+2+64) + 22 = 166 bytes FSP payload. With SessionDatagram + link overhead: 166 + 36 + 37 = 239 bytes on the wire.

CoordsRequired (0x20)

Plaintext error signal — transit node lacks coordinates for destination. Identified by the FSP phase field, not a separate message type byte (same as SessionSetup/SessionAck/SessionMsg3). Encoded with FSP prefix: ver=0, phase=0x0, U flag set, payload_len=34.

CoordsRequired

Payload (after 4-byte FSP prefix):

OffsetFieldSizeDescription
0msg_type1 byte0x20
1flags1 byteReserved
2dest_addr16 bytesNodeAddr we couldn't route to
18reporter16 bytesNodeAddr of reporting router

Payload size: 34 bytes. Total on wire: 38 bytes (4 prefix + 34 payload).

PathBroken (0x21)

Plaintext error signal — greedy routing reached a dead end. Encoded with FSP prefix: ver=0, phase=0x0, U flag set, payload_len.

PathBroken

Payload (after 4-byte FSP prefix):

OffsetFieldSizeDescription
0msg_type1 byte0x21
1flags1 byteReserved
2dest_addr16 bytesUnreachable NodeAddr
18reporter16 bytesNodeAddr of reporting router
34last_coords_count2 bytes LENumber of stale coordinate entries
36last_known_coords16 x n bytesStale coordinates that failed

MtuExceeded (0x22)

Plaintext error signal — forwarded packet exceeds the next-hop link MTU. Sent by a transit router back to the source when a SessionDatagram cannot be forwarded because its size exceeds the outgoing link's MTU. Encoded with FSP prefix: ver=0, phase=0x0, U flag set, payload_len=36.

MtuExceeded

Payload (after 4-byte FSP prefix):

OffsetFieldSizeDescription
0msg_type1 byte0x22
1flags1 byteReserved
2dest_addr16 bytesNodeAddr of the destination being forwarded to
18reporter16 bytesNodeAddr of the router that detected the MTU violation
34mtu2 bytes LEBottleneck MTU at the reporting router

Payload size: 36 bytes. Total on wire: 40 bytes (4 prefix + 36 payload).

The source uses the reported MTU to adjust its session-layer path MTU estimate. MtuExceeded is the reactive complement to the proactive path_mtu field in SessionDatagram and LookupResponse.

Encapsulation Walkthrough

A complete picture of how application data is wrapped through each layer.

Application Data -> Wire

Starting with an IPv6 application sending a 1024-byte TCP payload to a destination (the original IPv6 packet at the TUN is 1064 bytes: 40-byte header + 1024-byte payload):

Layer 5: Application data
    1024 bytes (TCP payload inside 1064-byte IPv6 packet)

Layer 4: IPv6 shim compression (port 256)
    Strip IPv6 addresses (32) + payload length (2), keep residual fields
    format (1) + residual (6) + upper payload (1024) = 1031 bytes

Layer 3: Session encryption (FSP)
    FSP header (12) + AEAD(inner_hdr (6) + port_hdr (4) + shim (1031)) + tag (16)
    = 12 + 1041 + 16 = 1069 bytes

Layer 2: SessionDatagram envelope (FMP routing)
    msg_type (1) + ttl (1) + path_mtu (2) + src_addr (16) + dest_addr (16) + payload (1069)
    = 1105 bytes

Layer 1: Link encryption (FMP per-hop)
    outer header (16) + encrypted(inner_hdr (5) + datagram (1105)) + AEAD tag (16)
    = 1142 bytes

Layer 0: Transport
    UDP datagram containing 1142 bytes

Total overhead for IPv6 traffic: 1142 − 1064 = 78 bytes per packet. The difference from the FIPS_IPV6_OVERHEAD constant (77 bytes) is the 1-byte FMP msg_type counted in the link inner header rather than the SessionDatagram body.

Overhead Budget

LayerOverheadComponent
Link encryption37 bytes16 outer header (AAD) + 5 inner header (timestamp + msg_type) + 16 AEAD tag
SessionDatagram body35 bytes1 ttl + 2 path_mtu + 16 src + 16 dest (msg_type counted in inner header)
FSP header12 bytes4 prefix + 8 counter
FSP inner header6 bytes4 timestamp + 1 msg_type + 1 inner_flags (inside AEAD)
Session AEAD tag16 bytesPoly1305 tag on session-encrypted payload
Protocol envelope106 bytesFIPS_OVERHEAD constant
Port header4 bytessrc_port + dst_port (DataPacket only)
DataPacket total110 bytesBase overhead for any port-multiplexed service
IPv6 compression−33 bytes40-byte IPv6 header → 7-byte format + residual
IPv6 data path total77 bytesFIPS_IPV6_OVERHEAD constant

At Each Transit Node

1. Receive UDP datagram
2. Parse common prefix -> version, phase, flags, payload_len
3. Phase 0x0 -> established frame
4. Look up (transport_id, receiver_idx) -> session
5. Check replay window (counter)
6. Decrypt with link keys (16-byte header as AAD) -> plaintext
7. Strip inner header -> timestamp, msg_type
8. msg_type 0x00 -> SessionDatagram
9. Read dest_addr -> routing decision
10. Decrement ttl, min path_mtu
11. Re-encrypt with next-hop link keys
12. Send via next-hop transport

Transit nodes see the SessionDatagram envelope (src_addr, dest_addr, ttl, path_mtu) but cannot read the session-layer payload (encrypted with endpoint session keys).

Size Summary

FMP Handshake Messages (Noise IK)

MessageRaw NoiseWire Frame
IK msg1 (ephemeral + encrypted static + encrypted epoch)106 bytes114 bytes
IK msg2 (ephemeral + encrypted epoch)57 bytes69 bytes

FSP Handshake Messages (Noise XK)

MessageRaw NoiseNotes
XK msg1 (ephemeral only)33 bytesCarried in SessionSetup
XK msg2 (ephemeral + encrypted epoch)57 bytesCarried in SessionAck
XK msg3 (encrypted static + encrypted epoch)73 bytesCarried in SessionMsg3
MessageSizeNotes
TreeAnnounce100 + 32n bytesn = depth + 1
FilterAnnounce1,035 bytesv1 (1KB filter)
LookupRequest46 + 16n bytesn = origin depth + 1
LookupResponse93 + 16n bytesn = target depth + 1
SessionDatagram36 + payload bytesFixed 36-byte header
Disconnect2 bytes

Session-Layer Messages (inside SessionDatagram)

MessageTypical SizeNotes
SessionSetup~170 bytesDepth-dependent (XK msg1 = 33 bytes)
SessionAck~190 bytesDepth-dependent, carries both endpoints' coords (XK msg2 = 57 bytes)
SessionMsg3~80 bytesFixed (XK msg3 = 73 bytes, no coords)
Data (minimal)12 + 6 + 4 + payload + 16 bytesSteady state (port header included)
Data (with coords)12 + ~130 + 6 + 4 + payload + 16 bytesWarmup/recovery (port header included)
SenderReport12 + 6 + 46 + 16 bytesMMP metrics
ReceiverReport12 + 6 + 66 + 16 bytesMMP metrics
PathMtuNotification12 + 6 + 2 + 16 bytesMTU signal
CoordsWarmup12 + coords + 6 + 16 bytesStandalone warmup (empty body)
CoordsRequired38 bytesFixed (prefix + msg_type + body)
PathBroken35 + 16n bytesIncludes stale coords
MtuExceeded40 bytesFixed (prefix + msg_type + body)
ScenarioWire SizeNotes
Encrypted frame minimum37 bytesEmpty body
SessionDatagram + Data (minimal)37 + 35 + 12 + 6 + 4 + payload + 16110 + payload (any service)
SessionDatagram + IPv6 Data (minimal)110 + 7 + upper_payload − 3477 + IPv6 payload (compressed)
SessionDatagram + Data (with coords)110 + coords + payloadCoords vary with tree depth
SessionDatagram + SessionSetup~275 bytesDepth-3, both dirs
SessionDatagram + CoordsRequired37 + 36 + 38 = 111 bytesIncluding link overhead

References