Container format
Twelve bytes of header, then the body. Everything a decoder needs to identify, size-check and verify the payload before touching it.
- 00
- magic · 2 B
- ASCII "FW" — 0x46 0x57
- 02
- version · 1 B
- Format version, currently 1
- 03
- flags · 1 B
- Bit field, see below
- 04
- length · 4 B
- Body length in bytes, uint32 BE
- 08
- crc32 · 4 B
- CRC-32 of body, uint32 BE
- 12
- body · n B
- length bytes of body
Header fields
magic (offset 0, 2 bytes)
MUST be the two ASCII bytes F (0x46) and W (0x57). Decoders MUST treat any other value as "not a container". The magic is deliberately short: the header competes for space with the payload in a 16×16 icon (756 usable bytes), and a two-byte magic combined with the version check gives a false-positive rate on random pixel data of roughly one in sixteen million.
version (offset 2, 1 byte)
MUST be 0x01 for this specification. A decoder that encounters an unknown version MUST NOT attempt to interpret the remaining fields and SHOULD report unsupported. See §12 for how future versions are introduced.
flags (offset 3, 1 byte)
- bit 0 · 0x01
- encrypted (§6)
- bit 1 · 0x02
- compressed (§5)
- bits 2–7
- reserved, MUST be written 0, MUST be ignored by decoders
Bits 2–7 are reserved and MUST be written as zero. Decoders MUST ignore reserved bits they do not understand rather than rejecting the container; this is what allows backward-compatible additions within version 1.
length (offset 4, 4 bytes)
Unsigned 32-bit big-endian count of body bytes. A decoder MUST check length ≤ capacity(size, mode) before reading the body; a length that exceeds what the image can physically hold is a definitive sign the header is not genuine (random pixels that happened to spell "FW\x01") and MUST be reported as not-a-container, not as truncation.
crc32 (offset 8, 4 bytes)
CRC-32 as defined in ISO 3309 / ITU-T V.42 and used by PNG and zlib: reflected polynomial 0xEDB88320, initial value 0xFFFFFFFF, final XOR 0xFFFFFFFF. It is computed over the body only — never the header — so that the header can be validated independently and so that the same body has the same CRC regardless of flag changes in future versions.
const TABLE = new Uint32Array(256)
for (let n = 0; n < 256; n++) {
let c = n
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
TABLE[n] = c >>> 0
}
export function crc32(bytes: Uint8Array): number {
let crc = 0xffffffff
for (const b of bytes) crc = TABLE[(crc ^ b) & 0xff] ^ (crc >>> 8)
return (crc ^ 0xffffffff) >>> 0
}Body
The body is the payload transformed by the pipeline payload → [deflate-raw] → [encrypt] → body, applying each step only when its flag is set. Order matters: compression MUST precede encryption, because ciphertext is incompressible and a compressor run after encryption would waste bytes and leak nothing useful.
Worked example
The 11-byte payload <h1>Hi</h1> does not shrink under deflate, so the encoder stores it raw with flags = 0. The complete container is 23 bytes:
46 57 magic "FW"
01 version 1
00 flags (none)
00 00 00 0b length 11
a7 ad b6 8f crc32 0xA7ADB68F
3c 68 31 3e 48 69 3c 2f 68 31 3e body "<h1>Hi</h1>"Header inspector
Type anything and watch the 12-byte header, the first 32 body bytes, and the resulting 16×16 noise icon update live.