Apache Iggy
Binary Protocol

Framing

Every message on a binary transport is a fixed 256-byte header followed by an optional body. The header is a #[repr(C)] struct decoded by pointer cast (zero-copy), so field offsets are fixed and enforced at compile time in the server. Three header shapes cross the client boundary:

  • RequestHeader: client to server.
  • ReplyHeader: server to client, answers one request.
  • EvictionHeader: server to client, session-terminal rejection with no body.

The header shape is identified by the command byte at offset 60. Values a client sends or receives:

commandValueDirectionMeaning
Request5client to serverA command request
Reply8server to clientAnswer to one request
Eviction13server to clientSession is dead, no per-request correlation

Other command values (Prepare, PrepareOk, view-change traffic, and so on) are replica-to-replica consensus messages and never appear on a client connection. They are documented on the Server-to-server page.

The size field at offset 48 is the total frame length in bytes, header included. A body, when present, immediately follows the 256 header bytes and has length size - 256. Readers can decode size before typing the header. That offset is a protocol constant.

RequestHeader

Client to server. 256 bytes.

OffsetSizeFieldTypeDescription
016checksumu128Frame seal. Not used on client-facing frames; send zero.
1616checksum_bodyu128Body seal. Not used on client-facing frames; send zero.
3216clusteru128Cluster id. Send zero.
484sizeu32Total frame length: 256 + body length.
524viewu32Consensus view. Send zero.
564releaseu32Must be zero.
601commandu85 (Request).
611replicau8Send zero.
6266reservedbytesZero.
12816clientu128Client-chosen session identity, non-zero. Minted fresh for each registration.
14416request_checksumu128Optional integrity stamp over the request body (the Rust SDK uses XxHash3-64 widened to u128). Lets the server's client table catch a request number reused for different arguments. Zero disables the comparison. Stamped only for metadata-plane operations; zero for partition-plane and non-replicated ones.
1608timestampu64Informational; the server echoes it into ReplyHeader.timestamp. May be zero.
1688requestu64Request number, per client. See request numbering.
1761operationu8The Operation discriminant.
1777paddingbytesZero.
1848sessionu64Session fence epoch from the login reply. Zero on the login-register request itself and on non-replicated operations sent before login (only PING is accepted unauthenticated).
1924user_idu32Ignored on the wire; the server stamps the authenticated user itself. Send zero.
19660reservedbytesZero, except: for operation = 2 (NonReplicated) bytes 196..200 carry the u32 command code, little-endian.

Operation discriminants

The operation byte tells the server which state-machine operation the request carries. Replicated operations are identified by operation alone. Non-replicated operations (reads, ping) all use operation = 2 and carry their concrete u32 command code in header bytes 196..200.

Values a client may send:

ValueOperationPlane
1Registersession (login handshake, codes 40 and 45)
2NonReplicatedreads and ping; the u32 code rides bytes 196..200
3Logoutsession (code 39)
128CreateStreammetadata
129UpdateStreammetadata
130DeleteStreammetadata
131PurgeStreammetadata
132CreateTopicmetadata
133UpdateTopicmetadata
134DeleteTopicmetadata
135PurgeTopicmetadata
136CreatePartitionsmetadata
137DeletePartitionsmetadata
138DeleteSegmentsmetadata (resolved to an internal partition truncation)
139CreateConsumerGroupmetadata
140DeleteConsumerGroupmetadata
141CreateUsermetadata
142UpdateUsermetadata
143DeleteUsermetadata
144ChangePasswordmetadata
145UpdatePermissionsmetadata
146CreatePersonalAccessTokenmetadata
147DeletePersonalAccessTokenmetadata
148JoinConsumerGroupmetadata
149LeaveConsumerGroupmetadata
160SendMessagespartition
161StoreConsumerOffsetpartition
162DeleteConsumerOffsetpartition

Value 0 is reserved and rejected. The 64..127 range is reserved for server-internal operations (currently 64 through 68 are assigned) and every value in it is refused from clients.

The planes matter for delivery semantics:

  • Metadata operations replicate through the metadata consensus group. The server deduplicates them by (client, request) and caches replies, so a retried request gets the cached answer instead of a double apply (exactly-once).
  • Partition operations replicate through their partition's consensus group. They are at-least-once: no reply cache, a replay may apply again.
  • Non-replicated operations are reads. They bypass consensus and deduplication entirely.

Request numbering

request is a per-client counter the server's client table tracks for metadata-plane operations:

  • Metadata operations must send a strictly increasing request (the SDK advances the counter per metadata request).
  • Partition operations and non-replicated operations send the current counter value without advancing it. The server doesn't track theirs.
  • The counter is a watermark, not a contiguous sequence: any value above the last accepted one is admissible.

session is the fence epoch: the value handed back by the login reply. Every request after login must echo it. When the same client id registers again, the new registration mints a higher epoch and requests carrying the old one are fenced (rejected as zombies).

ReplyHeader

Server to client. 256 bytes, followed by size - 256 bytes of body.

OffsetSizeFieldTypeDescription
016checksumu128Zero on client-facing frames.
1616checksum_bodyu128Zero on client-facing frames.
3216clusteru128Cluster id.
484sizeu32Total frame length: 256 + body length.
524viewu32Consensus view the reply was produced in.
564releaseu32Zero.
601commandu88 (Reply).
611replicau8Answering replica index.
6266reservedbytesZero.
12816request_checksumu128Echoed from the request.
14416contextu128Server context.
16016clientu128Echoed client id.
1768opu64Log position of the committed operation.
1848commitu64Commit point at reply time.
1928timestampu64Echo of the request timestamp.
2008requestu64Echoed request number; correlate replies by this.
2081operationu8Echoed operation discriminant.
2097paddingbytesZero.
2164statusu320 = accepted. Nonzero = an IggyError code for a failure decided before commit (authorization denial, admission reject). A nonzero status always comes with an empty body.
22036reservedbytesZero.

Decode order for a client:

  1. Read 256 bytes, check command. 13 (Eviction) means the session is dead: map the eviction reason to an error and stop. 8 (Reply) continues.
  2. Read the remaining size - 256 body bytes.
  3. If status is nonzero, the request failed pre-commit. The status value is the IggyError code and the body is empty.
  4. Otherwise decode the body. For result-framed operations, strip the result section first.

Result section

Replies for all metadata operations and for the partition-plane consumer-offset writes (StoreConsumerOffset, DeleteConsumerOffset) are result-framed: the body starts with a committed-result section ahead of the typed payload.

[count: u32]
count x { index: u32, result: u32 }
  • Success: count = 0, and the typed response payload (if any) follows the 4 count bytes.
  • Committed business rejection: one entry { index: 0, result: error_code } and no payload.

The header status channel and the result section are mutually exclusive by construction: a reply either failed pre-commit (status nonzero, empty body) or committed (status zero, result section present). A login-register reply carries the result section only when non-empty. On success its body starts directly with the login response payload.

Replies to non-replicated commands aren't result-framed: after status = 0 the body is the response payload directly.

EvictionHeader

Server to client. 256 bytes, never a body. An eviction is session-terminal: it says "this session is dead", carries no per-request correlation, and a client should deinitialize and re-login.

OffsetSizeFieldTypeDescription
016checksumu128Zero on client-facing frames.
1616checksum_bodyu128Zero on client-facing frames.
3216clusteru128Cluster id.
484sizeu32Always 256: an eviction has no body.
524viewu32
564releaseu32Zero.
601commandu813 (Eviction).
611replicau8
6266reservedbytesZero.
12816clientu128The evicted client id.
1444server_protocol_versionu32Accepted protocol window (max), packed semver. Set only for reason 14, zero otherwise.
1484server_protocol_version_minu32Accepted protocol window (min). Set only for reason 14, zero otherwise.
152103reservedbytesZero.
2551reasonu8Eviction reason, see below.

Eviction reasons:

ValueReasonMeaning
1NoSessionNo session exists for this client id
2ClientReleaseTooLowClient release below cluster minimum
3ClientReleaseTooHighClient release above cluster maximum
4InvalidRequestOperationUnknown operation discriminant
5InvalidRequestBodyBody failed validation
6InvalidRequestBodySizeBody size mismatch
7SessionTooLowSession epoch below the cluster's retained minimum
8SessionReleaseMismatchSession bound to a different release
9InvalidCredentialsLogin refused: bad username or password
10InvalidTokenLogin refused: bad personal access token
11UserInactiveLogin refused: user inactive
12SessionErrorSession-level failure
13StaleClientMissed heartbeats; server evicted the session
14IncompatibleProtocolClient protocol version outside the accepted window (see bytes 144..152)
15MalformedLoginLogin body without a decodable version prefix, or a legacy login code

Value 0 is reserved and never sent.

On this page