On this page· 5
File layout
- file format 6
- Rust SDK 1.0 preview
Plain words: the cover line
A pack is a text section, then an optional binary appendix, then a fixed 96-byte footer. Offsets on this page count from 0 at the first byte of the file, and L is the length of the file in bytes.
Figure 1 A pack with an appendix
Figure 2 A pack without an appendix
T = L − 96.| Offset | Part | Size | Present |
|---|---|---|---|
| 0 | header line | 257 bytes: 256 bytes of text and space fill, then one line feed (LF, the byte 0x0A) | always |
| 257 | records | one line per record, each ending in LF | always; may hold no records |
| after the last record | marker line | the text ===PLXI_BINARY_APPENDIX===, then LF | only with an appendix |
| T | padding | (64 − T mod 64) mod 64 bytes, each 0x00 | only with an appendix |
| A | appendix | csdt_size bytes | only with an appendix |
| L − 96 | footer | 96 bytes | always |
T is the offset of the first byte after the text section. A is T rounded up to a multiple of 64, and the appendix ends where the footer starts: A + csdt_size = L − 96. Without an appendix, A and csdt_size are 0.
A file shorter than 353 bytes, 257 plus 96, is not a pack. A reader rejects it with invalid_header.
The header line is ASCII text: six tokens with exactly one space between them, spaces up to byte 256, then LF. Its grammar, in ABNF (the grammar notation of RFC 5234):
header-line = header-content padding LF ; exactly 257 octets
header-content = magic SP version SP records SP csdt-offset SP csdt-size SP sha-field
magic = %x50.4C.58.49 ; "PLXI"
version = %x76 dec ; "v" then the format version, "v6"
records = %x72.65.63.6F.72.64.73.3D dec ; "records=" N
csdt-offset = %x63.73.64.74.5F.6F.66.66.73.65.74.3D dec ; "csdt_offset=" O
csdt-size = %x63.73.64.74.5F.73.69.7A.65.3D dec ; "csdt_size=" C
sha-field = %x73.68.61.32.35.36.3D [sha-hex] ; "sha256=" H or "sha256=" (placeholder)
sha-hex = 64lhex
lhex = %x30-39 / %x61-66 ; lowercase hexadecimal only
dec = %x30 / (%x31-39 *19%x30-39) ; decimal, no sign, no leading zeros, value < 2^64
padding = *SP ; space-fill so header-content + padding = 256 octets
SP = %x20
LF = %x0A
The header line of the quickstart's pack, without its space fill:
PLXI v6 records=3 csdt_offset=0 csdt_size=0 sha256=068f161e337da4b2b0c319a3539b9c62f4b15715d0f3c4de8e1664e5ef66bb28
| # | Token | Meaning | Rule |
|---|---|---|---|
| 1 | PLXI | the magic word | exactly these four bytes |
| 2 | v6 | the file format version | any other number is rejected with unsupported_version |
| 3 | records=N | the number of record lines | decimal, no sign, no leading zeros, below 2^64 |
| 4 | csdt_offset=O | where the appendix starts | 0 without an appendix; otherwise a multiple of 64 |
| 5 | csdt_size=C | the length of the appendix in bytes | 0 without an appendix |
| 6 | sha256=H | the digest | 64 lowercase hexadecimal digits, or nothing in a placeholder header |
The header line is outside the digest. A reader checks every value it takes from the header line against the footer.
A writer first emits a placeholder header, exactly PLXI v6 records=0 csdt_offset=0 csdt_size=0 sha256= and space fill, and overwrites it with the final header once the rest is written. The fixed width makes that possible without moving any other byte.
A writer that cannot seek backwards, such as one writing into a gzip stream, leaves the placeholder in place. For such a file the footer is the only source of the counts, offsets and digest. A header line that is neither final nor a placeholder is rejected with header_footer_mismatch.
Note
write and write_to_path in the Rust SDK always emit a final header.
The footer is the last 96 bytes of the file. Its integers are unsigned and little-endian: least significant byte first. The footer is the authoritative source for every count and offset.
| # | Offset | Size | Field | Type | Rule |
|---|---|---|---|---|---|
| 1 | 0 | 4 | magic | bytes | 50 4C 58 46, the ASCII text PLXF |
| 2 | 4 | 4 | version | u32 | 6 |
| 3 | 8 | 8 | record_count | u64 | the number of record lines |
| 4 | 16 | 8 | text_section_size | u64 | T |
| 5 | 24 | 8 | csdt_offset | u64 | A; 0 without an appendix |
| 6 | 32 | 8 | csdt_size | u64 | 0 without an appendix |
| 7 | 40 | 4 | csdt_file_checksum | u32 | a copy of the appendix's own file checksum; 0 without an appendix |
| 8 | 44 | 20 | reserved | bytes | written as zero; a nonzero byte is invalid_footer |
| 9 | 64 | 32 | sha256 | bytes | the digest, raw |
The same table as a byte map: Spec §10.0
The quickstart's pack is 544 bytes: header line at 0, records from 257 to 447, footer from 448. With no appendix, T is 448, which is L − 96.
| Field | Bytes in the file (hexadecimal) | Value |
|---|---|---|
magic | 50 4c 58 46 | PLXF |
version | 06 00 00 00 | 6 |
record_count | 03 00 00 00 00 00 00 00 | 3 |
text_section_size | c0 01 00 00 00 00 00 00 | 448 |
csdt_offset | eight 00 bytes | 0 |
csdt_size | eight 00 bytes | 0 |
csdt_file_checksum | four 00 bytes | 0 |
reserved | twenty 00 bytes | zero |
sha256 | 06 8f 16 1e … ef 66 bb 28 | the digest printed by the quickstart |
c0 01 read least significant byte first is 0x01c0, which is 448.