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 Group6
Command Code82
Server functioncmd_qmail_object_get_range in qmail_object_transfer.c
Wire structuresqmail_object_get_range_req_t, qmail_object_get_range_resp_t
Body layoutPreamble (48) + Common prefix (16) + Range request (48) = 112 bytes request header
TransportTCP only
EncryptionRequired — AES-128
AccessOwner-neutral, allow-all (Phase I)
Range capqmail_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)

object_id (16 bytes) 64 79 FT 80 RF 81 reserved (6) 82 87 generation (8, BE — 0 = latest committed) 88 95 offset (8, BE) 96 103 requested_length (4, BE) 104 107 reserved (4) 108 111
OffsetSizeFieldDescription
64–7916object_idThe stable object identifier.
801file_typeSame encoding as cmd 70/74: 0x00 meta, 0x01 body, 0x0A+ attachments.
811request_flagsMust be zero in v1.
82–876reservedMust be zero.
88–958generationUnsigned big-endian. 0 requests the latest committed generation; a specific value pins the read to that generation. See Generation pinning.
96–1038offsetUnsigned big-endian byte offset into the object.
104–1074requested_lengthUnsigned big-endian. Must be at least 1 and no more than qmail_max_download_range_bytes.
108–1114reservedMust be zero.
last 22TerminatorFixed 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.

FieldSizeDescription
common_prefix16Echoes request_id.
object_id16Echo of the requested object_id.
file_type1Echo of the requested file_type.
response_flags1Bit 0 = this range reaches end of the object. Bit 1 = the selected storage class is volatile.
hash_algorithm11 = SHA-256, matching object_hash below.
reserved1Zero.
generation8The generation this range was actually read from.
offset8Echo of the request's offset.
data_length4Actual number of data bytes returned. May be less than requested_length.
recommended_length4Server-suggested range size for the next call, same value as object_info.
total_size8The full stored size of this generation, for bounding subsequent range requests.
object_hash32SHA-256 of the complete object at this generation, for verification once all ranges are assembled.
reserved4Zero.
dataNRaw object bytes, length data_length.

Status codes

CodeSymbolMeaning
250SUCCESSData returned; see response body.
198ERROR_INVALID_PARAMETERrequest_flags is nonzero, or requested_length is zero.
202ERROR_FILE_NOT_EXISTNo committed object exists at this object_id/file_type/generation, or a delete tombstone is now current and this generation is no longer readable.
218ERROR_TCP_REQUIREDRetry using TCP.
219ERROR_UNSUPPORTED_PROTOCOLUnsupported protocol_version or framing version.
221ERROR_RANGE_TOO_LARGErequested_length exceeds the server's current configured maximum.
229ERROR_INVALID_RANGEoffset 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.