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.

The parts, in order

Figure 1 A pack with an appendix

Not to scale. Dashed parts exist only when the pack has an appendix. The header line and the footer are outside the digest.

Figure 2 A pack without an appendix

Without an appendix the footer follows the last record line, so T = L − 96.
Table 1. Parts of a pack: 6
OffsetPartSizePresent
0header line257 bytes: 256 bytes of text and space fill, then one line feed (LF, the byte 0x0A)always
257recordsone line per record, each ending in LFalways; may hold no records
after the last recordmarker linethe text ===PLXI_BINARY_APPENDIX===, then LFonly with an appendix
Tpadding(64 − T mod 64) mod 64 bytes, each 0x00only with an appendix
Aappendixcsdt_size bytesonly with an appendix
L − 96footer96 bytesalways

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

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 grammar (specification §3.1)abnf
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

Table 2. Header tokens: 6
#TokenMeaningRule
1PLXIthe magic wordexactly these four bytes
2v6the file format versionany other number is rejected with unsupported_version
3records=Nthe number of record linesdecimal, no sign, no leading zeros, below 2^64
4csdt_offset=Owhere the appendix starts0 without an appendix; otherwise a multiple of 64
5csdt_size=Cthe length of the appendix in bytes0 without an appendix
6sha256=Hthe digest64 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.

Final and placeholder headers

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.

Table 3. Footer fields: 9 fields, 96 bytes
#OffsetSizeFieldTypeRule
104magicbytes50 4C 58 46, the ASCII text PLXF
244versionu326
388record_countu64the number of record lines
4168text_section_sizeu64T
5248csdt_offsetu64A; 0 without an appendix
6328csdt_sizeu640 without an appendix
7404csdt_file_checksumu32a copy of the appendix's own file checksum; 0 without an appendix
84420reservedbyteswritten as zero; a nonzero byte is invalid_footer
96432sha256bytesthe digest, raw

The same table as a byte map: Spec §10.0

A real pack, decoded

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.

Table 4. The footer of the quickstart's pack: 9 fields
FieldBytes in the file (hexadecimal)Value
magic50 4c 58 46PLXF
version06 00 00 006
record_count03 00 00 00 00 00 00 003
text_section_sizec0 01 00 00 00 00 00 00448
csdt_offseteight 00 bytes0
csdt_sizeeight 00 bytes0
csdt_file_checksumfour 00 bytes0
reservedtwenty 00 byteszero
sha25606 8f 16 1e … ef 66 bb 28the digest printed by the quickstart

c0 01 read least significant byte first is 0x01c0, which is 448.

Rebuild this file with standard tools

Sections