This document defines the common message framing shared by all transports. Every message on the wire — whether carried over UDS, Named Pipe, or SHM — uses these exact byte layouts. All implementations (C, Rust, Go) must produce and consume identical bytes for the same logical message.
All multi-byte fields use host byte order (localhost-only IPC).
Every message begins with this fixed header.
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | u32 | magic | Must be 0x4e495043 ("NIPC") |
| 4 | 2 | u16 | version | Must be 1 |
| 6 | 2 | u16 | header_len | Must be 32 |
| 8 | 2 | u16 | kind | Message kind |
| 10 | 2 | u16 | flags | Bit flags |
| 12 | 2 | u16 | code | Method id or control opcode |
| 14 | 2 | u16 | transport_status | Envelope-level status |
| 16 | 4 | u32 | payload_len | Bytes after the header |
| 20 | 4 | u32 | item_count | 1 for single, N for batch |
| 24 | 8 | u64 | message_id | Request/response correlation |
Total: 32 bytes. Enforced by compile-time assertions.
| Value | Name | Meaning |
|---|---|---|
| 1 | REQUEST | Client-to-server request |
| 2 | RESPONSE | Server-to-client response |
| 3 | CONTROL | Protocol control message (handshake) |
| Bit | Name | Meaning |
|---|---|---|
| 0 | BATCH | 0x0001 — payload contains batch item directory + packed items |
These are envelope-level / protocol-level only. They never represent business or method outcomes.
| Value | Name | Meaning |
|---|---|---|
| 0 | OK | Message processed successfully at envelope level |
| 1 | BAD_ENVELOPE | Malformed header or payload structure |
| 2 | AUTH_FAILED | Authentication token rejected |
| 3 | INCOMPATIBLE | Protocol version or limit negotiation failed |
| 4 | UNSUPPORTED | Requested profile or method not supported |
| 5 | LIMIT_EXCEEDED | Payload or batch count exceeds negotiated limit |
| 6 | INTERNAL_ERROR | Unrecoverable internal error |
Used in the code field when kind = CONTROL:
| Value | Name | Meaning |
|---|---|---|
| 1 | HELLO | Client initiates handshake |
| 2 | HELLO_ACK | Server responds to handshake |
Used in the code field when kind = REQUEST or kind = RESPONSE:
| Value | Name | Meaning |
|---|---|---|
| 1 | INCREMENT | Test/benchmark method (u64 ping-pong) |
| 2 | CGROUPS_SNAPSHOT | Cgroups snapshot refresh |
| 3 | STRING_REVERSE | Test method (variable-length string ping-pong) |
New methods are assigned sequential codes. The method code space is shared across all services — each method has a globally unique code.
When flags & BATCH is set and item_count > 1, the payload begins
with an item directory followed by packed item payloads.
Batches are homogeneous: the outer header's code field identifies the
method type for all items in the batch. Mixed-method batches are not
representable in the wire format.
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | u32 | offset | Byte offset from start of packed item area |
| 4 | 4 | u32 | length | Byte length of item payload |
Items are aligned to 8-byte boundaries within the packed item area. Encoders must insert padding between items as needed. Item offsets must be multiples of 8.
[item_ref[0] ... item_ref[item_count-1]] (item_count * 8 bytes)
[padding to 8-byte boundary if needed]
[item 0 payload] (aligned to 8 bytes)
[padding]
[item 1 payload] (aligned to 8 bytes)
[padding]
...
[item N-1 payload] (aligned to 8 bytes)
When item_count = 1 and BATCH flag is not set, there is no item
directory. The payload follows the header directly as one self-contained
method/control payload.
When a message exceeds the negotiated packet size, it is split into chunks. The first chunk carries the original outer message header. Continuation chunks (chunk_index > 0) carry this header instead:
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | u32 | magic | Must be 0x4e43484b ("NCHK") |
| 4 | 2 | u16 | version | Must be 1 |
| 6 | 2 | u16 | flags | Reserved, must be 0 |
| 8 | 8 | u64 | message_id | Must match the original message |
| 16 | 4 | u32 | total_message_len | Total original message size (header + payload) |
| 20 | 4 | u32 | chunk_index | 0-based chunk sequence number |
| 24 | 4 | u32 | chunk_count | Total number of chunks |
| 28 | 4 | u32 | chunk_payload_len | This chunk's payload size in bytes |
Total: 32 bytes.
- The first chunk contains the original 32-byte outer header plus as many payload bytes as fit in one packet.
- Continuation chunks contain this 32-byte chunk header plus payload bytes.
- Chunk payload budget per packet = negotiated_packet_size - 32 bytes.
chunk_countmust be > 0.chunk_indexmust be <chunk_count.chunk_payload_lenmust be > 0.total_message_lenmust be > 0.- The receiver validates
magic,message_id,chunk_index, andchunk_counton every continuation chunk. Any mismatch is a protocol violation.
Sent by the client as a CONTROL message with code = HELLO.
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 2 | u16 | layout_version | Must be 1 |
| 2 | 2 | u16 | flags | Reserved, must be 0 |
| 4 | 4 | u32 | supported_profiles | Bitmask of client's supported profiles |
| 8 | 4 | u32 | preferred_profiles | Bitmask of client's preferred profiles |
| 12 | 4 | u32 | max_request_payload_bytes | Client's proposed whole-request payload ceiling |
| 16 | 4 | u32 | max_request_batch_items | Client's proposed request batch-item ceiling |
| 20 | 4 | u32 | max_response_payload_bytes | Client hint for response payload ceiling |
| 24 | 4 | u32 | max_response_batch_items | Client hint for response batch-item ceiling; kept for wire symmetry |
| 28 | 4 | - | padding | Reserved, must be 0 |
| 32 | 8 | u64 | auth_token | Caller-supplied authentication token |
| 40 | 4 | u32 | packet_size | Client's transport packet size |
Total: 44 bytes.
Sent by the server as a CONTROL message with code = HELLO_ACK.
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 2 | u16 | layout_version | Must be 1 |
| 2 | 2 | u16 | flags | Reserved, must be 0 |
| 4 | 4 | u32 | server_supported_profiles | Server's supported profiles |
| 8 | 4 | u32 | intersection_profiles | AND of client and server support |
| 12 | 4 | u32 | selected_profile | Profile chosen for this session |
| 16 | 4 | u32 | agreed_max_request_payload_bytes | Final request payload limit for this session |
| 20 | 4 | u32 | agreed_max_request_batch_items | Final request batch-item limit for this session |
| 24 | 4 | u32 | agreed_max_response_payload_bytes | Final response payload limit for this session |
| 28 | 4 | u32 | agreed_max_response_batch_items | Final response batch-item limit for this session; symmetric with request batch items |
| 32 | 4 | u32 | agreed_packet_size | Final packet size for this session |
| 36 | 4 | - | padding | Reserved, must be 0 |
| 40 | 8 | u64 | session_id | Server-assigned session identifier |
Total: 48 bytes.
The handshake is a proposal/result protocol:
- the client sends
HELLO - the server validates it, makes all final decisions, and returns
HELLO_ACK - the client uses the values returned in
HELLO_ACK - if the server rejects the handshake, it sends
HELLO_ACKwith a non-OKtransport_statusand then closes
The server does not return partial success. A successful handshake means:
transport_status = OK- all session-operational fields in
HELLO_ACKare final for that session selected_profileis final and locked for that session
If selected_profile is an SHM profile, the server must not send successful
HELLO_ACK until the SHM resources for that session are already ready on the
server side. If the client later cannot attach the negotiated SHM transport,
it must close that session and recover only via a new connection and new
handshake without SHM. The handshake must never claim SHM success and then
fall back within the same session.
The following matrix is normative.
Client input in HELLO |
Server validation / decision | Server return in HELLO_ACK |
Session uses |
|---|---|---|---|
layout_version |
Must be exactly 1, otherwise reject with INCOMPATIBLE |
none on failure, or layout_version = 1 on success |
layout_version = 1 |
flags |
Must be 0, otherwise reject with BAD_ENVELOPE |
none on failure, or flags = 0 on success |
flags = 0 |
auth_token |
Exact-match authorization check | no negotiated auth field; result is carried only in transport_status |
authorization outcome only |
supported_profiles + preferred_profiles |
Compute intersection = client_supported & server_supported; if empty reject with UNSUPPORTED; otherwise choose one final profile |
server_supported_profiles, intersection_profiles, selected_profile |
selected_profile |
max_request_payload_bytes |
Client proposes the whole request payload ceiling. If proposal exceeds 1 MiB, reject with LIMIT_EXCEEDED. Otherwise echo it unchanged. |
agreed_max_request_payload_bytes |
agreed_max_request_payload_bytes |
max_request_batch_items |
Echo unchanged unless a concrete protocol-level constraint is introduced in a future revision | agreed_max_request_batch_items |
agreed_max_request_batch_items |
max_response_payload_bytes |
Treat as a client hint only; server decides the final response payload ceiling it will use | agreed_max_response_payload_bytes |
agreed_max_response_payload_bytes |
max_response_batch_items |
Kept on the wire for symmetry; server must return the same effective batch-item limit as request batching for that session | agreed_max_response_batch_items |
agreed_max_response_batch_items, which must equal agreed_max_request_batch_items |
packet_size |
Choose min(client_packet_size, server_packet_size); if result is <= 32, reject with INCOMPATIBLE |
agreed_packet_size |
agreed_packet_size |
| no client field | Server allocates a fresh session identifier | session_id |
session_id |
Clarifications:
max_request_payload_bytesis the maximum size of the complete Level 1 request payload, excluding the 32-byte outer header.- For batch requests, this means the entire batch payload:
- item directory
- alignment padding
- all packed item payloads
max_request_batch_itemsandmax_response_batch_itemsare item-count ceilings, enforced independently from payload-byte ceilings.
On handshake rejection, the server sends HELLO_ACK with a non-OK
transport_status, then closes the connection.
| Condition | transport_status |
|---|---|
| auth token mismatch | AUTH_FAILED |
| no mutually supported profile | UNSUPPORTED |
layout_version != 1 |
INCOMPATIBLE |
negotiated packet size <= 32 |
INCOMPATIBLE |
proposed max_request_payload_bytes > 1 MiB |
LIMIT_EXCEEDED |
| malformed reserved/padding fields | BAD_ENVELOPE |
The session_id is a server-generated monotonic counter (starting at 1,
incremented per accepted session). It uniquely identifies this session for
the server's lifetime. When the negotiated profile is an SHM transport,
both sides use session_id to derive the per-session SHM region path or
kernel object name. See the POSIX SHM and Windows SHM transport contracts
for derivation rules.
Profile bits are transport-specific. POSIX and Windows use the same bit positions where possible:
| Bit | Value | POSIX name | Windows name |
|---|---|---|---|
| 0 | 0x01 |
UDS_SEQPACKET | NAMED_PIPE |
| 1 | 0x02 |
SHM_HYBRID | SHM_HYBRID |
| 2 | 0x04 |
SHM_FUTEX | SHM_BUSYWAIT |
| 3 | 0x08 |
— | SHM_WAITADDR |
Bit 0 is always the baseline profile for the platform.
- Client sends
HELLOwith:- authorization token
- supported and preferred profiles
- request payload and batch-item proposals
- response payload and batch-item hints
- packet size proposal
- Server validates the envelope and
HELLOpayload shape. - Server validates authorization.
- Server computes the mutually supported profile set.
- Server chooses one final
selected_profile:- first choice: highest bit in
(intersection & client_preferred & server_preferred) - fallback choice: highest bit in
intersection
- first choice: highest bit in
- Server computes the final session limits using the matrix above.
- If the final profile is SHM, the server makes SHM for that session ready
before sending successful
HELLO_ACK. - Server allocates
session_id. - Server sends
HELLO_ACKwithtransport_status = OK. - Both sides use the returned
HELLO_ACKvalues for the rest of the session.
| Constant | Value | Description |
|---|---|---|
| MAX_PAYLOAD_DEFAULT | 1024 | Default single-payload ceiling in bytes |
Receivers must validate on every received message:
magicmust be0x4e495043(or0x4e43484bfor chunk continuation)versionmust be1header_lenmust be32kindmust be 1, 2, or 3payload_lenmust not exceed the negotiated directional limititem_countmust not exceed the negotiated directional batch item limit- For batch messages: the item directory must fit within
payload_len, and all item offsets + lengths must fall within the packed item area - For chunk continuations:
message_id,chunk_index, andchunk_countmust be consistent with the in-progress chunked receive
Any validation failure is a protocol violation and results in session termination.