QMail Object Get Range — Group 6, Code 82
Downloads an arbitrary byte range of a committed object. The recipient calls this after object_info, using the generation it returned, until it has read the whole object.
Quick reference
| Command Group | 6 |
| Command Code | 82 |
| Server function | cmd_qmail_object_get_range in qmail_object_transfer.c |
| Wire structures | qmail_object_get_range_req_t, qmail_object_get_range_resp_t |
| Body layout | Preamble (48) + Common prefix (16) + Range request (48) = 112 bytes request header |
| Transport | TCP only |
| Encryption | Required — AES-128 |
| Access | Owner-neutral, allow-all (Phase I) |
| Range cap | qmail_max_download_range_bytes (server-configured; advertised by cmd 83) |
Purpose
Unlike the fixed 256 KB pages of cmd 74 download, object_get_range lets the client request any byte offset and length, independent of the upload chunk boundaries used by object_put_range. The server may return fewer bytes than requested (e.g. if the request crosses end-of-object or exceeds an internal buffer size); the response's data_length reports the actual count. The response also echoes recommended_length — the same value returned by object_info — so a client sizing its next range request has a server-suggested value to use.
Request body
Preamble (48 bytes, offsets 0–47)
Standard QMail preamble. See QMail Overview — Universal preamble.
Common prefix (16 bytes, offsets 48–63)
See object_begin for the shared common-prefix field table. command_header_length must equal 48 for this command.
Range request payload (48 bytes, offsets 64–111)
| Offset | Size | Field | Description |
|---|---|---|---|
| 64–79 | 16 | object_id | The stable object identifier. |
| 80 | 1 | file_type | Same encoding as cmd 70/74: 0x00 meta, 0x01 body, 0x0A+ attachments. |
| 81 | 1 | request_flags | Must be zero in v1. |
| 82–87 | 6 | reserved | Must be zero. |
| 88–95 | 8 | generation | Unsigned big-endian. 0 requests the latest committed generation; a specific value pins the read to that generation. See Generation pinning. |
| 96–103 | 8 | offset | Unsigned big-endian byte offset into the object. |
| 104–107 | 4 | requested_length | Unsigned big-endian. Must be at least 1 and no more than qmail_max_download_range_bytes. |
| 108–111 | 4 | reserved | Must be zero. |
| last 2 | 2 | Terminator | Fixed clear-text terminator. |
Response body
Successful response header length: 104 bytes (16-byte common prefix + 88-byte range response), followed by up to data_length bytes of object data.
| Field | Size | Description |
|---|---|---|
| common_prefix | 16 | Echoes request_id. |
| object_id | 16 | Echo of the requested object_id. |
| file_type | 1 | Echo of the requested file_type. |
| response_flags | 1 | Bit 0 = this range reaches end of the object. Bit 1 = the selected storage class is volatile. |
| hash_algorithm | 1 | 1 = SHA-256, matching object_hash below. |
| reserved | 1 | Zero. |
| generation | 8 | The generation this range was actually read from. |
| offset | 8 | Echo of the request's offset. |
| data_length | 4 | Actual number of data bytes returned. May be less than requested_length. |
| recommended_length | 4 | Server-suggested range size for the next call, same value as object_info. |
| total_size | 8 | The full stored size of this generation, for bounding subsequent range requests. |
| object_hash | 32 | SHA-256 of the complete object at this generation, for verification once all ranges are assembled. |
| reserved | 4 | Zero. |
| data | N | Raw object bytes, length data_length. |
Status codes
| Code | Symbol | Meaning |
|---|---|---|
| 250 | SUCCESS | Data returned; see response body. |
| 198 | ERROR_INVALID_PARAMETER | request_flags is nonzero, or requested_length is zero. |
| 202 | ERROR_FILE_NOT_EXIST | No committed object exists at this object_id/file_type/generation, or a delete tombstone is now current and this generation is no longer readable. |
| 218 | ERROR_TCP_REQUIRED | Retry using TCP. |
| 219 | ERROR_UNSUPPORTED_PROTOCOL | Unsupported protocol_version or framing version. |
| 221 | ERROR_RANGE_TOO_LARGE | requested_length exceeds the server's current configured maximum. |
| 229 | ERROR_INVALID_RANGE | offset is past the end of the object, or offset + length overflows. |
Generation pinning
A client must call object_info once, record the returned generation, and pass that same value on every object_get_range call used to reconstruct one file. Because replace publishes a new generation atomically, a client that instead sends generation=0 (latest) on every range call risks having a concurrent replace change the current generation partway through the download, mixing bytes from two different generations. Once a delete tombstone becomes current, the server rejects new range requests for every prior generation, even ones inside the generation grace period for object_info lookups.
Common mistakes
Assuming data_length always equals requested_length
The server may return fewer bytes than requested for reasons unrelated to end-of-object (internal buffering, storage-class characteristics). Always advance the read offset by the response's data_length, not by the request's requested_length.
Upload chunk size does not bound download range size
The accepted_chunk negotiated at object_begin only constrains upload ranges. Download range size is governed independently by qmail_max_download_range_bytes, discoverable via object_capabilities.