# KBQR1 wire format (version 1, frozen)

Each QR contains exactly one ASCII string, without leading/trailing whitespace:

    KBQR1|<id>|<i>|<total>|<sha256>|<body>

- `KBQR1` is the case-sensitive format/version tag.
- `id`: 16 lowercase hexadecimal characters; identifies this payload/layout.
- `i`: zero-based frame number, canonical decimal (no leading zero except 0).
- `total`: canonical decimal, number of all frames INCLUDING manifest 0.
- `sha256`: 64 lowercase hexadecimal characters, SHA-256 of the original UTF-8
  bytes before chunking. Repeated in every frame, including manifest 0.
- `body`: canonical unpadded base64url (RFC 4648 URL-safe alphabet). No '='.
- Exactly six pipe-separated fields. The body alphabet cannot contain a pipe.

Encode original text to UTF-8 without normalization or BOM insertion. Divide
bytes into fixed-size chunks (default 160); only the last may be shorter.
Individual chunks can split a multibyte UTF-8 character. Do NOT decode chunks
as text separately. For an empty payload, create one empty data chunk.

Let `n = max(1, ceil(byteLength / chunkSize))`. `total = n + 1`.
Frames 1 through n carry data chunks. Body of index 0 is UTF-8 JSON encoded
with the same unpadded base64url function. Its JSON is the four-element array:

    [byteLength, total, chunkSize, label]

The checksum and payload id for the manifest are already in its frame header;
they are not duplicated inside the JSON array. The optional label is represented
as an empty string when absent. Length is UTF-8 byte length, not JS string length.
The date is print presentation metadata, not part of the wire format.

The id is the first 16 hex characters of SHA-256 over UTF-8 bytes of the exact
JavaScript JSON.stringify result for `[checksum, chunkSize, label]`, with no
whitespace or trailing newline. Non-JS implementations must match JSON.stringify
escaping: quote/backslash and U+0000–001F escapes, no gratuitous ASCII or Unicode
escaping, literal well-formed Unicode elsewhere. See tests/known-vector.json.
This binds label and layout; changing chunkSize or label produces a different
set id even when payload bytes are unchanged. ECC, physical size, interval and
date do not affect identity. The short id is for selection; always verify the
full SHA-256 as well. A checksum is not authentication.

A receiver may begin with any frame. Validate the header and canonical base64url
before locking to that set. Require identical id/total/checksum on all frames.
Identical duplicates are idempotent. Conflicting duplicates throw, retaining
existing state. Enforce configurable or documented resource bounds before
allocating. This implementation allows 1 MiB data, 4096 frames, 2048 characters
per wire frame, 32–1024 byte chunks and 80 UTF-8 bytes of label.

On receiving frame 0, validate its schema, size/count formula, label and id.
Verify the length of each stored or arriving data chunk against the manifest.
When all frames are present, concatenate frame 1..n in order. Verify byte length
and SHA-256, then decode UTF-8 with fatal error handling. Only now return text.
Never return partial or unverified content. Any invalid final candidate leaves
receipt state unchanged; reset/rescan if an earlier collected chunk was corrupt.

No compression, encryption, fountain/erasure codes, signatures, binary-file
container or cross-version negotiation is implied. Lost codes cannot be replaced
by other codes. To add incompatible wire features, choose a new version tag and
keep KBQR1 decoding available. Optical symbology is independent of this grammar.
