Wire Primitives
This page is the normative reference for the encoding building blocks used throughout the Zenoh protocol. Every higher-level message is composed exclusively of these primitives.
Notation
| Symbol | Meaning |
|---|---|
|
Fixed 1-byte unsigned integer |
|
Fixed 2-byte unsigned integer, little-endian |
|
VLE-encoded unsigned integer, underlying type |
|
VLE-encoded unsigned integer, underlying type |
|
VLE-encoded unsigned integer, underlying type |
|
VLE-encoded unsigned integer, underlying type |
|
Byte array: |
|
Byte array: |
|
UTF-8 string: |
|
VLE-encoded integer field in a byte diagram |
|
Variable-length byte sequence in a byte diagram |
|
Fixed-width byte boundary in a byte diagram |
|
Field present only when flag |
All multi-byte fixed-width integers use little-endian byte order. Bit positions within a byte are numbered from the least-significant bit (bit 0) to the most-significant bit (bit 7).
Stream Framing (TCP Length Prefix)
Stream-oriented transports (TCP, TLS, QUIC) do not preserve message boundaries. Every transport message sent over a stream transport MUST be prefixed with a 2-byte little-endian length field:
0 1 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | length (u16 LE) | total bytes that follow +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ message bytes ~ exactly `length` bytes +-------------------------------+
The maximum framed message length is 65 535 bytes (2¹⁶ − 1).
Datagram transports (UDP unicast, UDP multicast) MUST NOT prepend this prefix; the datagram boundary delimits the message.
Variable-Length Encoding (VLE / LEB128)
Zenoh uses Little-Endian Base-128 (LEB128) encoding for all variable-length unsigned integers.
Algorithm
Each encoded byte contributes 7 bits of value in little-endian order.
Bit 7 of each byte is the continuation flag: 1 means more bytes follow; 0 means this is the last byte.
encode_vle(n: uint):
loop:
b = n & 0x7F
n >>= 7
if n != 0: b |= 0x80 -- set continuation flag
emit b
if n == 0: break
decode_vle() -> uint:
n = 0; shift = 0
loop:
b = read_byte()
n |= (b & 0x7F) << shift
shift += 7
if (b & 0x80) == 0: break -- last byte
return n
Encoding Examples
Value Encoded bytes Notes ────────────────────────────────────────────────────────────── 0 0x00 single byte 127 0x7F single byte, max 1-byte value 128 0x80 0x01 0x80 = continuation; 0x01 = last 300 0xAC 0x02 44 | (2 << 7) = 300 16383 0xFF 0x7F max 2-byte value 16384 0x80 0x80 0x01 2³²-1 0xFF 0xFF 0xFF 0xFF 0x0F 5 bytes 2⁶⁴-1 0xFF×9 0x01 9 bytes
Length-Prefixed Byte Arrays
Locator
A Locator is encoded as a canonical UTF-8 string using the <utf8;z8> primitive above.
Canonical form:
<proto>/<address>[?<metadata>]
proto-
Lower-layer transport or link protocol identifier such as
tcp,udp,tls, orquic. address-
Transport-specific address string, for example
192.168.1.10:7447,[2001:db8::1]:7447, orlocalhost:7447. metadata(optional)-
Semicolon-separated
key=valuepairs. In canonical form the keys are sorted lexicographically.
Examples:
tcp/192.168.1.10:7447 udp/224.0.0.224:7447 tcp/[2001:db8::1]:7447 quic/example.net:7447?iface=en0
ZenohID (ZID)
A ZenohID is an opaque, variable-length node identifier of 1–16 bytes generated randomly at node startup.
ZID in extensions (SourceInfo, ResponderId, Timestamp)
When carried inside extension payloads the ZID is encoded with a length byte:
7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ |zid_len|X|X|X|X| upper 4 bits: encoded length; lower 4 bits: context-specific +-+-+-+-+-+-+-+-+ ~ ZID ~ exactly (1 + zid_len) bytes
zid_len(bits 7:4)-
Encoded length. Actual byte count =
1 + zid_len, giving range 1–16.
ZID in message bodies (INIT, JOIN, SCOUT, HELLO)
In these messages the ZID length shares a packed byte with other fields:
7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ |zid_len| misc | bits 7:4 = zid_len; bits 3:0 = message-specific +-+-+-+-+-------+ ~ ZID ~ (1 + zid_len) bytes, little-endian
The split of the lower 4 bits is message-specific (WhatAmI, flags, etc.).
WhatAmI (Node Role)
A 2-bit value encoding the role of a Zenoh node:
| Code | Name | Meaning |
|---|---|---|
|
Router |
Infrastructure node that forwards messages between sessions |
|
Peer |
Full participant, can connect to routers and other peers |
|
Client |
Leaf node, connects only to routers or peers |
|
Reserved |
MUST NOT be used |
In INIT, JOIN, and HELLO, WhatAmI occupies bits 1:0 of the packed ZID-length byte.
WhatAmIMatcher (Scout Bitmap)
A 3-bit bitmap for filtering which node roles should respond to a SCOUT:
| Bit | Role matched |
|---|---|
0 (value |
Router |
1 (value |
Peer |
2 (value |
Client |
Multiple bits may be set simultaneously (e.g., 0b011 = Router or Peer).
In SCOUT, the WhatAmIMatcher occupies bits 2:0 of the packed ZID-length byte.
Resolution Byte
A packed byte negotiated during INIT/JOIN that governs the wire widths of sequence numbers and request IDs:
7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ |X|X|X|X|RID|FSN| +-+-+-+-+-+-+-+-+
| Sub-field | Bits | Meaning |
|---|---|---|
|
1:0 |
Frame/Fragment Sequence Number resolution |
|
3:2 |
Request ID resolution |
Reserved |
7:4 |
MUST be zero |
| Code | Width | Maximum value |
|---|---|---|
|
8 bits |
255 |
|
16 bits |
65 535 |
|
32 bits |
4 294 967 295 (default) |
|
64 bits |
2⁶⁴ − 1 |
Default resolution byte: 0x0A (0b00001010, FSN = 32-bit = 0b10, RID = 32-bit = 0b10).
Sequence numbers and request IDs are carried as VLE-encoded integers on the wire; the resolution controls their maximum value, which governs wire cost at maximum utilisation.
WireExpr (Key Expression)
A WireExpr is a compressed representation of a key expression consisting of:
scope(ExprId, z16)-
Maps to a previously declared key expression (0 = global scope / no compression).
suffix(optional)-
A UTF-8 string appended to the scoped expression. Present when the
Nflag is set in the enclosing message header.
Wire encoding:
% key_scope : z16 % ExprId (0 = global scope) ~ key_suffix ~ if N flag set: <u8;z16> suffix string
Flags governing WireExpr appear in the enclosing message header byte:
| Flag | Bit | Meaning |
|---|---|---|
|
5 |
Named — the suffix field is present |
|
6 |
Mapping — |
Encoding Field
The Encoding field identifies the content type of a payload (analogous to a MIME type). It consists of a numeric ID and an optional opaque schema.
~ id_and_S : z32 ~ VLE z32; actual encoding_id = wire_value >> 1; S = wire_value & 0x01 ~ schema:<u8;z8>~ if S==1: opaque schema bytes
id-
31-bit encoding identifier (the upper 31 bits of the z32 wire value); in practice, current IDs fit within 15 bits.
S(Schema flag, bit 0)-
if
1, a<u8;z8>schema byte array follows.
The encoding registry is application-defined; well-known values include 0 (raw bytes) and 1 (UTF-8 text).
Timestamp (HLC / NTP64 + ZenohID)
A Zenoh Timestamp is a Hybrid Logical Clock (HLC) value from the uhlc library.
It combines a 64-bit NTP timestamp with the ZenohID of the generating node.
Wire encoding (as used in the Timestamp extension ZBuf payload):
% ntp64 : z64 % 64-bit NTP timestamp, VLE-encoded % zid_len: z8 % VLE-encoded ZID byte count (1–16) ~ ZID ~ zid_len bytes (ZenohID of the clock source)
The 64-bit NTP value uses the standard format: bits 63:32 are seconds since 1900-01-01; bits 31:0 are the fractional second. The HLC augments the fraction bits to embed a logical counter when multiple events occur within the same physical clock tick, ensuring strict monotonicity.
Extension Header (TLV)
Extensions are optional metadata blocks that follow the fixed fields of a message. Each extension is a Type-Length-Value triplet.
Extension Header Byte
7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ |Z|ENC|M| ID | +-+-+-+-+-------+
| Field | Bits | Meaning |
|---|---|---|
|
7 |
More — if 1, another extension follows immediately after this one |
|
6:5 |
Encoding — payload format (see table below) |
|
4 |
Mandatory — if 1, receiver MUST understand this extension |
|
3:0 |
Extension identifier (0x0–0xF) |
ENC Values
| Name | ENC bits |
Payload |
|---|---|---|
Unit |
|
No payload — header byte only |
Z64 |
|
One VLE-encoded u64 value ( |
ZBuf |
|
|
Reserved |
|
MUST NOT be used |
Extension Wire Formats
Unit extension: [header : u8] ENC=0b00, no further bytes Z64 extension: [header : u8] ENC=0b01 % value : z64 % VLE-encoded u64 ZBuf extension: [header : u8] ENC=0b10 % length : z32 % VLE-encoded byte count ~ [u8 × length] ~ raw bytes
Mandatory vs Optional
M=0-
Unknown extensions SHOULD be skipped (read and discard the payload) and SHOULD be forwarded unchanged to next hops. They MUST NOT cause an error.
M=1-
Unknown mandatory extensions MUST cause the containing message to be rejected. The connection MAY be closed with reason
UNSUPPORTED.
Extension Chain
The Z bit chains extensions together.
Z=1 means another extension immediately follows; Z=0 means this is the last extension:
[ext₁ header | Z=1] [ext₁ payload] [ext₂ header | Z=1] [ext₂ payload] [ext₃ header | Z=0] [ext₃ payload] ← last extension; message body follows [first message body field]
Message Header Byte
Every Zenoh message begins with a single header byte:
7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ |FL3|FL2|FL1| ID| +-+-+-+---------+
ID(bits 4:0)-
5-bit message identifier (see per-layer tables in Message Reference).
FL1(bit 5),FL2(bit 6)-
Message-specific flags.
FL3(bit 7)-
Always the Z (Extensions) flag: when set, one or more extension blocks follow the base message fields.