4.3 Message Framing

The protocol is binary and multiplexed. All multi-byte integers are little-endian.

4.3.1 The request header #

A request is 22 bytes of header followed by an operation-specific payload.

OffsetSizeField
04total_len
48request_id
122op_code
148txn_id

total_len is the whole message including the header.

request_id matches a response to its request. Request ids are allocated by the kernel, are strictly increasing within one connection, and are never reused while that connection lives — including after a request has timed out.

txn_id is the transaction the operation belongs to, or zero for none. When non-zero, the source MUST process the operation inside that transaction's context.

4.3.2 The response header #

A response is 14 bytes of header followed by a payload. It carries no txn_id.

OffsetSizeField
04total_len
48request_id
122op_code

A source MUST copy request_id from the request into its response, and MUST set op_code to the request's operation code with the high bit set — that is, op_code | 0x8000. Every operation has a named response code for this value.

Every response payload begins with a u32 status at offset 14, so the minimum response size is 18 bytes.

4.3.3 Encoding #

A length-prefixed field is a u32 byte count followed by that many bytes. Strings so encoded are UTF-8 and carry no terminator; a terminator byte counted in the length is a null byte and is therefore invalid. Some length-prefixed fields carry binary rather than text — Security Descriptors and value data — and are not UTF-8.

A GUID is 16 raw bytes.

An array is a u32 count followed by that many entries.

A boolean is one byte.

4.3.4 Extension, and its asymmetry #

Requests and responses extend differently, and a source MUST implement both rules.

A request MAY carry trailing fields beyond those a source recognises. A source MUST skip them, using total_len to find the end of the message. This is how the kernel adds an optional field without an RSI version bump.

A response MUST NOT. The kernel rejects any trailing bytes in a response payload as malformed data. A source MUST emit exactly the payload each operation defines, and no more. Extending the protocol in this direction is done with new operations, never by growing an existing payload.

4.3.5 Reading and writing #

The device fd is message-oriented. total_len frames messages, but a successful I/O call never splits or joins one.

read() returns exactly one complete request. If none is queued, a blocking read waits until one is, or until the fd is closing, in which case it returns 0. Under O_NONBLOCK an empty queue returns EAGAIN. If the caller's buffer is too small for the next queued request, read() returns EMSGSIZE and does not consume it.

write() submits exactly one complete response. The buffer length MUST equal the response header's total_len, and MUST be at least the response header size. A successful write is never short.

The following all fail EINVAL and tear the connection down, marking the source Down:

  • a length shorter than the response header;
  • a length that does not equal total_len;
  • an unknown request_id;
  • a response to a request that has not been delivered;
  • a second response to a request already answered;
  • an op_code that is not the request's with the response bit set;
  • a response written on a descriptor that is not the slot's active one.

A source that cannot frame its own messages correctly is not one whose other answers can be relied on. Ordinary per-operation errors are reported through the status vocabulary and do not tear anything down.

poll() reports the fd readable when at least one complete request is queued, writable while the slot is Active, and POLLHUP | POLLERR when the slot is Down or the fd is closing. An fd that is open but not yet registered reports nothing.

4.3.6 Concurrency and timing #

A source MAY process requests in any order and MUST handle multiple in-flight requests without head-of-line blocking. Responses are matched by request_id, not by arrival order.

The kernel bounds in-flight requests per source by MaxConcurrentRSIRequests, default 256.

A source MUST respond to every request it has read, exactly once, even if the kernel-side caller has already given up. The kernel applies a request timeout — RequestTimeoutMs, default 30 seconds, measured from the moment it first tries to reserve an in-flight slot and covering the whole wait — and a source is not disconnected for exceeding it. Late responses are validated and processed exactly like on-time ones.

A timed-out request remains in the kernel's in-flight table, and keeps occupying one of the source's slots, until the source answers or the connection is torn down. A source that accumulates unanswered requests will exhaust its own concurrency budget.

Edit this page