QMail Object Info — Group 6, Code 81
Returns committed metadata for an object — size, hash, generation, expiry — without transferring any object bytes. The recipient calls this before object_get_range to learn the current generation and plan its download.
Quick reference
| Command Group | 6 |
| Command Code | 81 |
| Server function | cmd_qmail_object_info in qmail_object_transfer.c |
| Wire structures | qmail_object_info_req_t, qmail_object_info_resp_t |
| Body layout | Preamble (48) + Common prefix (16) + Info request (32) = 96 bytes request header |
| Transport | TCP only |
| Encryption | Required — AES-128 |
| Access | Owner-neutral, allow-all (Phase I) |
Purpose
A reader who has an object_id (for QMail mail, derived from the email GUID — see Tell integration) and a file_type looks up the object's current state before downloading it. The response reports the committed generation, its exact size, its hash, when it was committed, and when it expires. generation=0 in the request means “the latest committed generation”; a specific nonzero value requests that exact generation if it is still within the configured generation grace period after being superseded.
The response is owner-neutral — any caller who can construct a valid encrypted QMail request and knows the object identifier receives the same info. Ownership only gates write operations (replace via object_begin, delete via object_delete).
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 32 for this command.
Info request payload (32 bytes, offsets 64–95)
| Offset | Size | Field | Description |
|---|---|---|---|
| 64–79 | 16 | object_id | The stable object identifier declared at begin. |
| 80 | 1 | file_type | Same encoding as cmd 70/74: 0x00 meta, 0x01 body, 0x0A+ attachments. |
| 81–87 | 7 | reserved | Must be zero. |
| 88–95 | 8 | generation | Unsigned big-endian. 0 requests the latest committed generation. |
| last 2 | 2 | Terminator | Fixed clear-text terminator. |
Response body
Successful response header length: 112 bytes (16-byte common prefix + 96-byte info response). No data follows.
| 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. |
| object_state | 1 | 1 = committed. |
| storage_class | 2 | The class the object is stored in. |
| hash_algorithm | 1 | 1 = SHA-256. |
| acl_version | 1 | 1 in Phase I. |
| object_flags | 2 | Bit 0 = volatile storage (may not survive a process or host failure). |
| generation | 8 | The generation this response describes. |
| total_size | 8 | The exact stored size of this generation. |
| recommended_length | 4 | The server's recommended range size for object_get_range calls against this object. |
| reserved | 4 | Zero. |
| committed_at | 8 | Unix seconds when this generation was published. |
| expires_at | 8 | Unix seconds. 0 means no scheduled expiry. |
| object_hash | 32 | The SHA-256 verified at commit time for this generation. |
Status codes
| Code | Symbol | Meaning |
|---|---|---|
| 250 | SUCCESS | Metadata returned; see response body. |
| 202 | ERROR_FILE_NOT_EXIST | No committed object exists at this object_id/file_type/generation — either it was never uploaded to this server, the current generation is a tombstone (deleted), or the requested generation is outside the generation grace period. |
| 218 | ERROR_TCP_REQUIRED | Retry using TCP. |
| 219 | ERROR_UNSUPPORTED_PROTOCOL | Unsupported protocol_version, framing version, or nonzero flags. |
Access control
In Phase I, reads are allow-all: any caller who can construct a valid encrypted QMail request and supplies the correct object_id and file_type receives the same response — there is no per-recipient authorization check. Allow-all does not mean anonymous plaintext access; transport authentication and encryption still apply. The server stores owner identity and acl_version now so a future phase can restrict reads without changing this response's shape.
A current tombstone (the object was deleted and no earlier generation is requested) returns ERROR_FILE_NOT_EXIST, the same code as an object that never existed — a reader cannot distinguish "never uploaded" from "deleted" through this command alone. Replaced (non-current) generations remain readable via an explicit nonzero generation request only during the configured generation grace period; after that they also return ERROR_FILE_NOT_EXIST.
Common mistakes
Not pinning the generation before downloading ranges
A client must call object_info first, record the returned generation, and pass that exact value to every subsequent object_get_range call for this reconstruction. Omitting the generation (or using 0 throughout) risks a concurrent replace changing the current generation mid-download, mixing bytes from two different generations in the reassembled file.
Treating ERROR_FILE_NOT_EXIST as a hard failure
For a striped upload spread across many servers, a single server returning ERROR_FILE_NOT_EXIST for one stripe is expected if that particular server never received the upload or lost it. The recipient's Reed-Solomon reconstruction tolerates a bounded number of missing stripes; treat this per-server, not as a failure of the whole reconstruction.