QMail Peek — Group 6, Code 73

Non-blocking inbox check. The client asks the recipient’s beacon RAIDA for all pending .tell notifications newer than a caller-supplied timestamp. The server returns immediately with a tell-list response, or an empty tell-list if there is no matching mail.

Quick reference

Command Group6 (QMail)
Command Code73 (0x49)
Server functioncmd_qmail_peek in cmd_qmail.c
Request packet32-byte plaintext RAIDA header + encrypted body fields + plaintext 3E 3E terminator
Request body size54 bytes total: encrypted challenge(16) + qmail identity/auth(32) + since_timestamp(4), then plaintext terminator(2)
Response packet32-byte plaintext RAIDA response header + encrypted tell-list payload + plaintext 3E 3E terminator
EncryptionRequired — AES-128-CTR, type 1, keyed by the caller’s selected coin AN for this RAIDA
Side effect on successEach returned .tell file is queued for deletion after it is read into the response

Purpose

peek is the non-blocking version of ping. It scans the recipient mailbox directory immediately, returns every .tell file whose filesystem modification time is greater than since_timestamp, and then closes the request. Pass 0 to return every tell currently in the inbox.

The server path is selected from the mailbox identity in the request body: QMAIL_MAILBOX_ROOT/{denom_hex}/{serial_number}/inbox/. The request must be sent to the recipient’s beacon RAIDA; other RAIDAs may not have that mailbox coin loaded.

Wire packet

The word header is used carefully on this page. The RAIDA packet header is the 32 plaintext bytes at the front of the TCP packet. The 16-byte challenge/CRC is encrypted with the rest of the request body. raidax groups that challenge with the QMail identity block and calls those first 48 decrypted body bytes qmail_preamble_t.

QMail Peek request packet, bytes on TCP

+-------------------------------+-----------------------------------------+----------------------+
| RAIDA request header           | encrypted request body                  | plaintext terminator |
| 32 bytes                       | 52 bytes                                | 2 bytes              |
+-------------------------------+-----------------------------------------+----------------------+
| command group=6, command=73    | challenge/CRC(16)                       | 3E 3E                |
| encryption type=1              | QMail identity/auth block(32)           |                      |
| body_size=54                   | since_timestamp(4)                      |                      |
| nonce(8)                       |                                         |                      |
+-------------------------------+-----------------------------------------+----------------------+

The header’s body_size field includes the encrypted body bytes and the terminator, so for peek it is always 54 (0x0036) in current rest_core.

Request header (32 bytes, plaintext)

OffsetSizeFieldPeek value / meaning
01Router version0x01
11Split ID0x00 for the standard single-frame request
21RAIDA IDTarget beacon RAIDA index, 0–24
31Shard IDCurrent rest_core sends shard 0x03
41Command Group0x06 (QMail)
51Command Code0x49 (73, peek)
6–72Coin ID00 06 CloudCoin network ID
81Bitfield0x01
9–157ReservedZero-filled by rest_core
161Encryption type0x01 AES-128-CTR with coin AN
171Encryption key denominationDenomination of the coin used for wire encryption
18–214Encryption key SNSerial number of the coin used for wire encryption, big-endian
22–232Body size00 36 for peek (54 bytes)
24–318NonceAES-CTR nonce used to encrypt/decrypt the request body

Request body (54 bytes after the RAIDA header)

Challenge random (12 bytes, part 1 of 2) 0 7 Challenge random (part 2) 8 11 Challenge CRC32 (BE) 12 15 Session ID (8 bytes, zeros for QMail) 16 23 Coin Type 24 25 DN 26 Mailbox SN (BE) 27 30 RS 31 Authenticity Number (16 bytes, part 1 of 2) 32 39 Authenticity Number (part 2 of 2) 40 47 since_timestamp (4 bytes, BE) 48 51 3E 3E (term) 52 53
Decrypted request body layout

+-------------------+--------------------------+---------------------+----------------------+
| challenge/CRC     | QMail identity/auth      | since_timestamp     | terminator           |
| 16 bytes          | 32 bytes                 | 4 bytes             | 2 bytes              |
| encrypted         | encrypted                | encrypted           | plaintext            |
+-------------------+--------------------------+---------------------+----------------------+
| body[0..15]       | body[16..47]             | body[48..51]        | body[52..53]         |
+-------------------+--------------------------+---------------------+----------------------+
Body offsetSizeEncrypted?FieldDescription
0–1112YesChallenge randomRandom bytes generated by the client.
12–154YesChallenge CRC32Big-endian CRC32 of body bytes 0–11. The server verifies this after decrypting.
16–238YesSession IDAll zeros for current QMail AES-128 requests.
24–252YesCoin Type00 06.
261YesMailbox denominationRecipient mailbox coin denomination.
27–304YesMailbox serial numberRecipient mailbox serial number, big-endian.
311YesReservedWas Device ID. Current server ignores it; clients should write zero.
32–4716YesAuthenticity NumberThe mailbox coin AN for this RAIDA. After decryption, process_inbox_scan() compares it to the stored AN for the body’s denomination/SN.
48–514Yessince_timestampBig-endian Unix seconds. The server returns only .tell files with st_mtime > since_timestamp.
52–532NoTerminatorFixed 3E 3E. Included in body_size but not encrypted.

raidax names body offsets 0–47 qmail_preamble_t. In the terminology above, the challenge is still body data because it is encrypted with the rest of the body fields.

Response packet

For encryption type 1, the response has the same legacy 32-byte RAIDA header size. If the command returns a tell-list payload, the server encrypts the payload bytes and appends an unencrypted 3E 3E terminator.

QMail Peek response packet, bytes on TCP

+-------------------------------+-----------------------------------------+----------------------+
| RAIDA response header          | encrypted tell-list payload             | plaintext terminator |
| 32 bytes                       | payload_len bytes                       | 2 bytes              |
+-------------------------------+-----------------------------------------+----------------------+
| status, group, body_size       | array header(8)                         | 3E 3E                |
| execution time                 | record 1                                |                      |
| challenge signature            | record 2                                |                      |
|                                | ...                                     |                      |
+-------------------------------+-----------------------------------------+----------------------+

The response header’s 3-byte body size is payload_len + 2. For an empty successful peek, payload_len is 8 and the response body size is 10.

Response header (32 bytes, plaintext)

OffsetSizeFieldDescription
01RAIDA IDResponding RAIDA index.
11Shard IDLegacy response path writes zero.
21Status0xFA on success, or an error code.
31Command Group0x06.
4–52Frame countLegacy response writes 00 01.
6–72Echo bytesEchoes legacy request bytes 30–31.
81ReservedZero.
9–113Body sizeBig-endian length of encrypted payload plus terminator.
12–154Execution timeServer execution time in microseconds, big-endian.
16–3116Challenge signatureLegacy response signature used by the client to verify the response belongs to the request.

Tell-list payload (encrypted)

Decrypted tell-list payload

+----------------------+--------------------------------------+--------------------------------+
| Array header          | Tell record 1                        | Tell record 2 ...              |
| 8 bytes               | 64 + (M * 32) + manifest_len bytes   | variable                       |
+----------------------+--------------------------------------+--------------------------------+
Payload offsetSizeFieldDescription
01tell_countNumber of tell records following this array header.
1–22total_tellsCurrently zero-filled by the server.
3–75reservedZero-filled.
8...Variabletell recordstell_count records, each parsed using the record structure below.

If the inbox directory does not exist or contains no matching files, the server returns STATUS_SUCCESS with an 8-byte all-zero payload (tell_count = 0). That is a successful empty peek.

Tell record structure

Each returned record is the full contents of one .tell file. The server does not add a per-record length field. For manifest v1 records, the client reads three fields from the 64-byte file header to compute the record size:

  • stripe_count (byte 29, called M): number of server-location entries.
  • file_count (byte 52, called N) and file_entry_size (byte 53, always 16): together describe the file manifest. The big-endian manifest_len at bytes 54–55 must equal N × file_entry_size.
manifest v1 record_size = 64 + (M * 32) + manifest_len
legacy v0 record_size   = 64 + (M * 32), then optionally skip an 18-byte footer

+-------------------------+-----------------------------+--------------------------------+
| QMail file header        | server location entries      | file manifest entries          |
| 64 bytes                 | M entries * 32 bytes         | N entries * file_entry_size    |
+-------------------------+-----------------------------+--------------------------------+

Current manifest v1 records do not include a trailing footer; the manifest entries are the last bytes of each record. Pre-manifest legacy records have manifest_version=0, no manifest entries, and may have an 18-byte recipient footer on disk (tag 0x50, length 16, recipient locker). A receiver that sees that footer after a legacy record should skip it before parsing the next record.

QMail file header (64 bytes)

Record offsetSizeFieldDescription
0–1516email_idMessage/file GUID. Used by download to fetch stored stripes.
16–172sender_coin_id00 06.
181sender_denominationSender mailbox denomination.
19–224sender_serial_numberSender mailbox serial number, big-endian.
231sender_device_idDevice tag copied from the tell blob. Logged, not enforced.
24–274timestampSender timestamp, big-endian Unix seconds.
281tell_typeCurrent QMail email tells use 0.
291stripe_count (M)Number of 32-byte server entries following this header. The tell sender validated this as 1–32.
30–4516locker_codeLocker/download key copied into the notification. The receiver passes this to download.
46–494total_file_sizeBig-endian original size of the body file only (file_type 0x01). Attachment original sizes live in the manifest entries.
501version0x02.
511manifest_version1 for current manifest records. 0 marks a legacy pre-manifest record.
521file_count (N)For manifest v1, number of files described by the manifest, including the body. Attachment count is N - 1. Legacy v0 records write 0.
531file_entry_sizeFor manifest v1, always 16. Legacy v0 records write 0.
54–552manifest_lenFor manifest v1, big-endian byte length of the manifest trailing section. Must equal N × file_entry_size. Legacy v0 records write 0.
561manifest_flagsFor manifest v1, bit 0 (footer_removed) is set and bit 1 (crc32_present) is set when the per-entry CRC32 field is populated. Bits 2–7 are reserved.
57–637reservedPass-through tail of the manifest header. Must be zero for manifest v1.

Server location entry (32 bytes each)

There are M entries immediately after the file header. This structure tells the recipient which RAIDA servers hold the stripes and how to connect to them for download.

Entry offsetSizeFieldDescription
01stripe_indexStripe number within the file’s stripe set.
11stripe_typeCurrent rest_core writes 0 for data and 1 for parity.
21server_idRAIDA index holding this stripe. This is stored in the first byte of the legacy stripe_id field.
3–97stripe_id reservedCurrently zero-filled by rest_core.
10–1910IPv6 prefixZero-filled for IPv4-mapped addresses.
20–212IPv4 markerFF FF for IPv4-mapped address.
22–254IPv4 addressStorage RAIDA IPv4 octets.
26–272PortStorage RAIDA TCP port, big-endian.
28–314reservedZero-filled by rest_core.

File manifest entries (N × 16 bytes)

The manifest is the authoritative list of files that belong to this email. The body is listed first with file_type=0x01; attachments follow using sequential file types 0x0A, 0x0B, and so on. The receiver uses original_size when sizing the reassembly buffer so padding bytes are trimmed correctly.

Entry offsetSizeFieldDescription
01file_type0x01 body, 0x0A attachment 1, 0x0B attachment 2, etc. First entry MUST be the body.
11file_flagsBit 0 = body; bit 1 = attachment; other bits reserved.
2–32reservedPass-through; must be zero.
4–118original_sizeBig-endian uint64 size before striping/padding. The receiver uses this to size each file’s reassembly buffer.
12–154crc32Big-endian CRC32 over the original file bytes. Optional: written only when the file header’s manifest_flags.crc32_present bit is set; zero otherwise. Receivers must only verify the CRC when the flag is set.

Local REST test calls

These calls use the two-client local setup: client1 is the sender on port 8081 and client2 is the receiver on port 8082. The peek response persists returned tells into client2’s local DB before the JSON response is sent, so the download call can use the same file_guid.

# Optional: stop client2 background beacon before the sender sends mail
GET http://127.0.0.1:8082/api/qmail/local/beacon/control?action=stop

# Non-blocking check; notifications include manifest_version, file_count, manifest_flags, and files[]
GET http://127.0.0.1:8082/api/qmail/net/beacon/peek?since=0

# Download the received email and all manifest-listed attachments
GET http://127.0.0.1:8082/api/qmail/net/messages/download?file_guid=<FILE_GUID>

# Inspect downloaded attachment rows and fetch metadata for one attachment
GET http://127.0.0.1:8082/api/qmail/db/attachments/list?email_id=<FILE_GUID>
GET http://127.0.0.1:8082/api/qmail/db/attachments/get?email_id=<FILE_GUID>&attachment_id=<ATTACHMENT_ID>&info=1

Status codes

DecimalHexSymbolMeaning
2500xFASTATUS_SUCCESSInbox scanned. Response payload is the tell-list, possibly empty.
80x08ERROR_COIN_NOT_FOUNDMailbox (denom, SN) from the body is not loaded on this RAIDA.
160x10ERROR_INVALID_PACKET_LENGTHRequest body is shorter than the required 48 + 4 + 2 bytes.
330x21ERROR_INVALID_EOFProtocol terminator was not 3E 3E.
340x22ERROR_INVALID_ENCRYPTIONServer could not decrypt/validate the body before reaching cmd_qmail_peek.
370x25ERROR_INVALID_CRCThe decrypted challenge CRC did not match.
1940xC2ERROR_FILESYSTEMInbox opendir() failed for a reason other than ENOENT.
2000xC8ERROR_INVALID_ANBody AN does not match the server’s stored AN for the body denomination/SN.
2540xFEERROR_MEMORY_ALLOCServer response buffer allocation failed.

The per-coin rate limiter can silently drop excessive requests by closing the connection without a status response.

Common mistakes

Confusing the packet header with qmail_preamble_t

The 32-byte RAIDA header is plaintext. The server’s 48-byte qmail_preamble_t is decrypted body data: challenge/CRC plus the QMail identity/auth block.

Forgetting that the terminator is not encrypted

The header body-size field includes the final 3E 3E, but AES-128-CTR is applied only to the bytes before it.

Computing record_size without reading the manifest header

Each record’s size depends on both stripe_count (byte 29) and manifest_len (bytes 54–55). A client that uses 64 + M*32 alone will land partway into the manifest of the first record and misparse every subsequent one. Always include manifest_len in the per-record advance.

Empty response is success

STATUS_SUCCESS with tell_count = 0 means the inbox was scanned and no matching tells were found.

Points of confusion

  1. Filter is mtime-based. The server compares each .tell file’s st_mtime to since_timestamp. It does not compare the timestamp embedded in the tell file header.
  2. Tells are deleted on read. A successful peek queues returned files for deletion after directory scanning. Multiple devices sharing one mailbox can race; only the first device receives a given tell.
  3. Records are variable length. There is no length prefix per record. Use stripe_count (byte 29) and manifest_len (bytes 54–55) from the file header to compute 64 + M*32 + manifest_len.
  4. The response can be capped. The server stops growing the response at its maximum tell response size; remaining tell files stay in the inbox for a later call.