The wire protocol reference implementation
Generated page
Model gemma-mtp, commit ef58254ca7be, 2026-08-16, sources: 6. Edit the code or the hand-written documentation instead.
Diagram
The Connection framing and length prefixing
The protocol uses a length-prefixed framing mechanism where each message is preceded by a 4-byte integer indicating the size of the following frame Protocol.kt:24. In the Python reference implementation, the _send method packs the API key, version, and correlation ID into a header before sending the payload, all wrapped in a length prefix wire.py:81-82. The _receive method performs the inverse, first reading the 4-byte length and then the exact number of bytes required to complete the frame wire.py:86-87.
Request decoding and DecodeResult
Decoding is a two-stage process designed to protect the broker. First, the RequestDecoder attempts to parse the header to identify the ApiKey and correlationId Requests.kt:79-96. If the header is valid, the decoder proceeds to parse the body; if the header itself is malformed, it returns a DecodeResult.Failed containing an UNKNOWN_CORRELATION_ID to prevent the broker attempting to respond to an invalid ID Requests.kt:97-98.
The Session protocol loop
The Session class manages the lifecycle of a single connection through a continuous loop in the serve function Session.kt:47-53. The loop follows a strict sequence:
readFrame(): Reads the next complete frame from the connectionSession.kt:50.handle(frame): Dispatches the decoded requestSession.kt:51.respondErrororwriteFully: Sends the response back to the clientbooblik-net/src/main/kotlin/ru/workinprogress/booblik/net/Session.kt:88, 151. This loop ensures that requests are served strictly in order, one at a time, to maintain consistency with the client's expectation of response orderingSession.kt:30-34.
The FetchMode and zero-copy path
The broker supports two distinct modes for transferring data during a fetch request Session.kt:219-230:
| Mode | Description |
|---|---|
FetchMode.ZERO_COPY | Uses connection.transferFrom to move data directly from the log segment to the socket Session.kt:221 |
FetchMode.HEAP | Reads data into a reusable staging buffer before writing to the connection Session.kt:225 |
Handling partial frames and connection survival
The protocol is designed to be robust against network fragmentation. A frame may be split across multiple TCP packets; the Session handles this by reading the full length of the frame before attempting to decode it Session.kt:68-71. However, if a frame claims an "absurd" length that exceeds Protocol.MAX_FRAME_BYTES, the broker throws a CorruptRequestException and drops the connection to protect its memory booblik-net/src/main/kotlin/ru/workinprogress/booblik/net/Session.kt:69, booblik-protocol/src/commonMain/kotlin/ru/workinprogress/booblik/net/wire/Protocol.kt:31. Tests in PartialFrameTest.kt:112-128 verify that while split packets are assembled correctly, invalid lengths result in connection closure.
The awaitRecords mechanism
When a FetchRequest includes a maxWaitMillis and minBytes requirement, the broker enters a suspension loop in awaitRecords Session.kt:246-267. Instead of busy-waiting, the coroutine suspends using handle.writer.highWatermark.first { it > seen }, which resumes only when the log's watermark advances Session.kt:261. This ensures the broker does not waste CPU cycles while waiting for more data to become available in the log.
Key files
| File | Lines | What is there |
|---|---|---|
…/wire/Protocol.kt | 15-58 | Protocol constants and API Key/Error Code definitions |
…/net/Session.kt | 37-298 | The main protocol loop and request handling logic |
…/wire/Requests.kt | 79-184 | Request data structures and the RequestDecoder |
Behaviour that does surprise
- Silent Silence: In
producerequests, if theackPolicyis set toACK_NONE, the broker returns no response at all rather than an empty response, because an offset does not exist until the writer reaches the batchwire.py:149-150. - The Metadata Exception: Unlike other requests,
MetadataRequestis dispatched before the topic and partition are even parsed, because it is the only request that does not name a specific partitionRequests.kt:110-115. - Clamped Waiting: If a client requests a
maxWaitMillislonger than 60 seconds, the broker clamps the wait toMAX_FETCH_WAIT_MILLISto prevent holding coroutines and sockets open indefinitelyProtocol.kt:44.