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 Group6
Command Code81
Server functioncmd_qmail_object_info in qmail_object_transfer.c
Wire structuresqmail_object_info_req_t, qmail_object_info_resp_t
Body layoutPreamble (48) + Common prefix (16) + Info request (32) = 96 bytes request header
TransportTCP only
EncryptionRequired — AES-128
AccessOwner-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)

object_id (16 bytes) 64 79 FT 80 reserved (7) 81 87 generation (8, BE — 0 = latest committed) 88 95
OffsetSizeFieldDescription
64–7916object_idThe stable object identifier declared at begin.
801file_typeSame encoding as cmd 70/74: 0x00 meta, 0x01 body, 0x0A+ attachments.
81–877reservedMust be zero.
88–958generationUnsigned big-endian. 0 requests the latest committed generation.
last 22TerminatorFixed clear-text terminator.

Response body

Successful response header length: 112 bytes (16-byte common prefix + 96-byte info response). No data follows.

FieldSizeDescription
common_prefix16Echoes request_id.
object_id16Echo of the requested object_id.
file_type1Echo of the requested file_type.
object_state11 = committed.
storage_class2The class the object is stored in.
hash_algorithm11 = SHA-256.
acl_version11 in Phase I.
object_flags2Bit 0 = volatile storage (may not survive a process or host failure).
generation8The generation this response describes.
total_size8The exact stored size of this generation.
recommended_length4The server's recommended range size for object_get_range calls against this object.
reserved4Zero.
committed_at8Unix seconds when this generation was published.
expires_at8Unix seconds. 0 means no scheduled expiry.
object_hash32The SHA-256 verified at commit time for this generation.

Status codes

CodeSymbolMeaning
250SUCCESSMetadata returned; see response body.
202ERROR_FILE_NOT_EXISTNo 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.
218ERROR_TCP_REQUIREDRetry using TCP.
219ERROR_UNSUPPORTED_PROTOCOLUnsupported 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.