QMail Object Commit — Group 6, Code 79

Verifies a transfer is complete and correct, then atomically publishes it as the object's new current generation. This is the last step of an upload started with object_begin.

Quick reference

Command Group6
Command Code79
Server functioncmd_qmail_object_commit in qmail_object_transfer.c
Wire structuresqmail_object_commit_req_t, qmail_object_commit_resp_t
Body layoutPreamble (48) + Common prefix (16) + Commit request (64) = 128 bytes request header
TransportTCP only
EncryptionRequired — AES-128
IdempotentYes — keyed by (authenticated owner, transfer_id)

Purpose

Once the client believes every byte has been uploaded (confirmed with object_transfer_status or simply by having sent every planned range), it calls object_commit. The server verifies complete byte coverage, that the declared total_size and hash_algorithm match what was accepted at begin, and computes the actual hash over every received byte — rejecting with ERROR_HASH_MISMATCH if it does not match the object_hash supplied here.

Only after all verification succeeds does the server atomically change the current-generation pointer to the target generation accepted at begin. Readers must always see either the complete old generation or the complete new generation, never a mixture — a reader that already has an object_info response pinned to the old generation continues to see it consistently even while a commit is in progress elsewhere.

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 64 for this command.

Commit request payload (64 bytes, offsets 64–127)

transfer_id (16 bytes) 64 79 total_size (8, BE) 80 87 HA 88 reserved (7) 89 95 object_hash (32 bytes, SHA-256 of the complete object) 96 127
OffsetSizeFieldDescription
64–7916transfer_idMust match the transfer_id returned by object_begin for this transfer.
80–878total_sizeUnsigned big-endian. Must match the value accepted at begin.
881hash_algorithmMust match the value accepted at begin (1 = SHA-256).
89–957reservedMust be zero.
96–12732object_hashSHA-256 the server verifies against the actual received bytes before publishing.
last 22TerminatorFixed clear-text terminator.

Response body

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

FieldSizeDescription
common_prefix16Echoes request_id.
object_id16The object's stable identifier, as declared at begin.
file_type1Echo of the file_type declared at begin.
object_state11 = committed.
storage_class2The class the object was actually stored in.
generation8The generation just published — equal to the target_generation accepted at begin.
total_size8Echo of the committed size.
hash_algorithm1Echo of the request's hash_algorithm.
reserved3Zero.
object_hash32Echo of the verified object hash.
committed_at8Unix seconds when the generation was published.

Status codes

CodeSymbolMeaning
250SUCCESSObject published; see response body.
167ERROR_PAYMENT_PROCESSINGPayment for this transfer's owner/object_id/locker_code key is still pending.
169ERROR_PAYMENT_REQUIREDPayment state is failed; commit cannot proceed.
218ERROR_TCP_REQUIREDRetry using TCP.
219ERROR_UNSUPPORTED_PROTOCOLUnsupported protocol_version, framing version, nonzero flags, or hash_algorithm.
222ERROR_TRANSFER_NOT_FOUNDtransfer_id is unknown for this authenticated owner.
223ERROR_TRANSFER_EXPIREDThe transfer existed but has expired.
225ERROR_TRANSFER_INCOMPLETECommit attempted with missing byte ranges.
226ERROR_HASH_MISMATCHThe complete-object hash computed by the server does not match the supplied object_hash.
231ERROR_OBJECT_STATEThe transfer is not in a committable state (already committed with different fields, or aborted).
234ERROR_TRANSFER_CONFLICTA retry of commit supplied different total_size, hash_algorithm, or object_hash than the original successful commit.

Idempotency

Commit is idempotent by owner and Transfer ID. If the commit response is lost in transit, an exact retry — identical total_size, hash_algorithm, and object_hash — returns the original successful commit's metadata rather than re-publishing or re-verifying. A retry with any different value returns ERROR_TRANSFER_CONFLICT, since that would mean the client is trying to commit a different object under the same Transfer ID.

Common mistakes

Committing before every range is uploaded

Calling commit while gaps remain returns ERROR_TRANSFER_INCOMPLETE, not a partial success. Use object_transfer_status with range_mode=0 to confirm zero missing ranges before committing, especially after resuming a dropped connection.

Computing object_hash over the wrong bytes

object_hash must be the hash of the exact bytes stored on this server — one stripe — not the sender's original, pre-striped file. Hashing the wrong data returns ERROR_HASH_MISMATCH even though every byte uploaded correctly.