Box Archive Format Specification

Version: 0.2.0
Status: Draft

1 Introduction

The Box format is an open archive format designed for storing files with support for compression, checksums, extended attributes, and memory-mapped access. It provides platform-independent path encoding and efficient metadata storage through string interning.

1.1 Design Goals

1.2 File Extension

Box archives SHOULD use the .box file extension.

2 Terminology

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Archive: A Box format file containing zero or more entries.

Entry: A file, directory, or symbolic link stored in the archive.

Record: The metadata structure describing an entry.

Trailer: The metadata section at the end of the archive containing all records and attributes.

Chunked File: A file stored with block-level compression, enabling random access without decompressing the entire file.

3 File Structure Overview

A Box archive consists of five sections laid out sequentially:

Offset 0x00 → Header 32 bytes, fixed size
Offset 0x20 → Data Section Variable size
header.trailer → Trailer Variable size (BoxMetadata)
Path FST Variable size
EOF → Block FST Variable size (optional)
Table 1: Box archive structure

The Header is always 32 bytes and located at offset 0. The Header contains a pointer to the Trailer, which stores record metadata. File content data is stored between the Header and Trailer. The Path FST follows the Trailer and provides path-to-record mapping (see Section 14). The Block FST, if present, follows the Path FST and provides logical-to-physical offset mapping for chunked files (see Section 15). Both FSTs are prefixed with a u64 length (little-endian), enabling efficient seeking past the Path FST to detect the Block FST’s presence.

All multi-byte integers in the Box format are stored in little-endian byte order unless otherwise specified.

4 Data Types

4.1 Fixed-Size Integers

Type Size Description
u8 1 byte Unsigned 8-bit integer
u32 4 bytes Unsigned 32-bit integer, little-endian
u64 8 bytes Unsigned 64-bit integer, little-endian
i64 8 bytes Signed 64-bit integer, little-endian

4.2 Vu64 Encoding (FastVint)

Variable-size unsigned 64-bit integers (Vu64) are encoded using FastVint, a prefix-based variable-length encoding where the number of leading zeros in the first byte determines the total byte count.

Length Determination:

The number of leading zero bits in the first byte, plus one, gives the total byte count:

1xxx_xxxx  →  1 byte   (7 data bits)
01xx_xxxx → 2 bytes (14 data bits: 6 + 8)
001x_xxxx → 3 bytes (21 data bits: 5 + 16)
0001_xxxx → 4 bytes (28 data bits: 4 + 24)
0000_1xxx → 5 bytes (35 data bits: 3 + 32)
0000_01xx → 6 bytes (42 data bits: 2 + 40)
0000_001x → 7 bytes (49 data bits: 1 + 48)
0000_0001 → 8 bytes (56 data bits: 0 + 56)
0000_0000 → 9 bytes (64 data bits: 0 + 64)

Offset-Based Encoding:

FastVint uses offset-based encoding for denser packing. Each length tier has a base offset:

Bytes Value Range Offset
1 0 – 127 0
2 128 – 16,511 128
3 16,512 – 2,113,663 16,512
4 2,113,664 – 270,549,119 2,113,664
5+ (continues exponentially)
9 (full u64 range) 72,624,976,668,147,840

Data Byte Order:

After the prefix byte, remaining data bytes are stored in big-endian order.

Key Properties:

4.3 String

Strings are encoded as:

[Vu64: byte length]
[UTF-8 bytes]

All strings MUST be valid UTF-8. Implementations MUST reject invalid UTF-8 sequences.

4.4 Vec<T>

Vectors (arrays) are encoded as:

[Vu64: element count]
[T encoding] * count

The following vector types are used in the format:

Type Usage Element Encoding
Vec<Record> BoxMetadata.records Record (see Section 8)
Vec<AttrKey> attr_keys Type tag + String (see Section 7.2)

4.5 Vec<u8> (Byte Array)

Byte arrays are encoded identically to strings:

[Vu64: byte length]
[raw bytes]

4.6 RecordIndex

A RecordIndex is a 1-based index into the records array, encoded as Vu64:

[Vu64: index value]

Index values MUST be non-zero. An index value of n refers to records[n-1].

4.7 AttrMap

Attribute maps are encoded as:

[u64: byte count of remaining section (not including this field)]
[Vu64: entry count]
For each entry:
[Vu64: key index into attr_keys]
[Vec<u8>: value]

The leading byte count allows implementations to skip the entire attribute map without parsing individual entries.

5 Header

The Header is located at byte offset 0 and is exactly 32 bytes.

5.1 Header Structure

Offset Size Field Description
0x00 4 magic Magic bytes: 0xFF 0x42 0x4F 0x58 (\xFFBOX)
0x04 1 version Format version number
0x05 1 flags Feature flags (see Section 5.4)
0x06 2 reserved1 Reserved bytes
0x08 4 alignment Data alignment boundary (u32)
0x0C 4 reserved2 Reserved bytes
0x10 8 trailer Byte offset to trailer (u64)
0x18 8 reserved3 Reserved bytes

Total Size: 32 bytes

5.2 Magic Bytes

The magic bytes MUST be exactly 0xFF 0x42 0x4F 0x58 (the byte 0xFF followed by ASCII BOX).

The leading 0xFF byte is invalid UTF-8, causing any attempt to parse the file as a text string to fail immediately at byte 0.

5.3 Version

The current format version is 0x01 (1). Implementations SHOULD reject archives with unknown major versions.

5.4 Flags

The flags byte is a bitfield:

Bit Name Description
0 EXTERNAL_SYMLINKS Archive contains external symlinks (see Section 8.5)
1 ALLOW_ESCAPES Archive paths may contain .. components
2-7 Reserved Must be 0

5.5 Alignment

The alignment field specifies the byte boundary to which file data is aligned. Valid values are powers of 2 from 1 to 65536. A value of 0 indicates no alignment (equivalent to 1).

When alignment is specified, padding bytes (value 0x00) are inserted before file data to ensure each file’s data starts at an offset that is a multiple of the alignment value.

Common alignment values:

5.6 Reserved Fields

All reserved fields (reserved1, reserved2, reserved3) MUST be set to zero when writing and MUST be ignored when reading to allow for future format extensions.

5.7 Binary Layout

Off 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0x00 FF 42 4F 58 VV XX RR RR AA AA AA AA RR RR RR RR
0x10 TT TT TT TT TT TT TT TT RR RR RR RR RR RR RR RR
Table 2: Header binary layout

Legend: FF 42 4F 58 = magic, VV = version, XX = flags, RR = reserved, AA = alignment, TT = trailer offset

6 Data Section

The Data Section immediately follows the Header and contains the actual file content.

6.1 Layout

File data is stored contiguously, with optional padding between files when alignment is enabled. The Data Section starts immediately after the Header. When alignment is disabled, this is offset 0x20 (32 bytes). When alignment is enabled, padding bytes (0x00) are inserted so that the first file’s data begins at an aligned offset. The Data Section extends to the trailer offset.

Offset 0x20 → [Padding] (if alignment > 1)
File 1 Data
[Padding] (if alignment > 1)
File 2 Data
[Padding]
header.trailer →
Table 3: Data section layout

6.2 Alignment Padding

When header.alignment > 1, padding bytes (0x00) are inserted before file data such that:

file_data_offset % alignment == 0

The amount of padding before each file is:

padding = (alignment - (current_offset % alignment)) % alignment

7 Trailer (Metadata)

The Trailer contains all archive metadata encoded as BoxMetadata.

7.1 BoxMetadata Structure

[Vec<AttrKey>: attr_keys]
[AttrMap: attrs]
[Vu64: dictionary_length]
[bytes: dictionary]
[Vec<Record>: records]

Fields are encoded in the order shown, using the data type encodings from Section 4. The dictionary field is encoded as a Vu64 length followed by raw bytes; length=0 means no dictionary is present.

7.2 Attribute Key Table (attr_keys)

The attribute key table provides string interning for attribute keys. Each entry consists of:

[u8: type tag]
[String: key name]

The type tag indicates the expected value type for this attribute key. See Section 7.3 for type tag values.

Keys are referenced by their 0-based index in this table.

7.3 Attribute Value Types

Type Tag Values:

Value Type Description
0 Bytes Raw bytes, no interpretation
1 String UTF-8 string
2 Json UTF-8 JSON
3 U8 Single byte (u8)
4 Vi32 Zigzag-encoded i32
5 Vu32 FastVint-encoded u32
6 Vi64 Zigzag-encoded i64
7 Vu64 FastVint-encoded u64
8 U128 Fixed 16 bytes (128 bits)
9 U256 Fixed 32 bytes (256 bits)
10 DateTime Minutes since Box epoch (2026-01-01 00:00:00 UTC), zigzag-encoded i64
11-255 Reserved Reserved for future use

When serializing: keys are written in symbol order (0, 1, 2, …).

When deserializing: strings are assigned symbols in the order read. The type tag determines how the attribute value is interpreted.

7.4 Archive Attributes (attrs)

The attrs field stores archive-level attributes as an AttrMap. Common archive attributes include creation tools, comments, or custom metadata.

7.5 Dictionary

The dictionary field contains a Zstd dictionary used for compression. When present (non-empty), all records compressed with Zstd MUST use this dictionary for both compression and decompression. The dictionary is ignored for other compression methods (Stored, XZ).

Dictionary training is typically performed at archive creation time using ZDICT_trainFromBuffer() or equivalent. A recommended dictionary size is 32KB-128KB, trained on representative samples from the archive contents totaling approximately 100× the dictionary size.

When dictionary is empty, Zstd compression operates without a dictionary.

The dictionary provides two benefits:

  1. Improved compression ratio, especially for small files with shared patterns
  2. More predictable compression ratios, which aids in estimating compressed block sizes for chunked files

8 Record Types

Records describe entries in the archive. Each record begins with a 1-byte type/compression identifier.

8.1 Type/Compression Byte

The first byte of each record encodes both the record type and compression method:

[type_compression: u8]

Layout:

Bits 0-3: Record type
Bits 4-7: Compression method

Record Type Values:

Value Type Description
0x01 Directory Directory entry
0x02 File Regular file
0x03 Symlink Internal symbolic link
0x0A Chunked File File with block-level compression
0x0B External Symlink Symbolic link to external target

Compression Values:

Value Method Description
0x00 Stored No compression
0x10 Zstd Zstandard compression
0x20 XZ XZ/LZMA2 compression

Combined Examples:

Byte Meaning
0x01 Directory (compression ignored)
0x02 File, stored (uncompressed)
0x12 File, Zstd compressed
0x22 File, XZ compressed
0x1A Chunked file, Zstd compressed blocks

8.2 Directory Record (Type 0x01)

Directories have no associated data in the Data Section.

Structure:

[type_compression: u8]  // Always 0x01
[String: name]
[AttrMap: attrs]

Binary Layout:

00 type_compression (directory)
String: name
AttrMap: attrs

8.3 File Record (Type 0x02)

Files store their content in the Data Section.

Structure:

[type_compression: u8]
[length: u64] // Compressed length in data section
[decompressed_length: u64] // Original file size
[data: u64] // Byte offset in archive
[String: name]
[AttrMap: attrs]

Binary Layout:

TT type_compression
LL LL LL LL LL LL LL LL length (u64)
DD DD DD DD DD DD DD DD decompressed_length (u64)
OO OO OO OO OO OO OO OO data offset (u64)
String: name
AttrMap: attrs

When compression == Stored, length == decompressed_length.

8.4 Symlink Record (Type 0x03)

Symbolic links point to other entries within the archive.

Structure:

[type_compression: u8]  // Always 0x03
[String: name]
[Vu64: target (RecordIndex)]
[AttrMap: attrs]

Constraints:

Binary Layout:

03 type_compression (symlink)
String: name
Vu64: target (RecordIndex)
AttrMap: attrs

8.5 External Symlink Record (Type 0x0B)

External symlinks point to paths outside the archive. This record type is only valid when the EXTERNAL_SYMLINKS flag is set in the header.

Structure:

[type_compression: u8]  // Always 0x0B
[String: name]
[String: target]
[AttrMap: attrs]

Target Path Format:

The target path uses BoxPath encoding (see Section 9) with one relaxation: .. components are permitted to allow the symlink to escape the archive root. The path is stored with \x1F (Unit Separator) as the component separator.

Constraints:

Binary Layout:

0B type_compression (external symlink)
String: name
String: target
AttrMap: attrs

Extraction Behavior:

When extracting an external symlink record, implementations:

  1. Convert the stored target path from BoxPath format (\x1F separators) to platform path format
  2. Create a symbolic link at the record’s location using the converted relative target path

Security Considerations:

External symlinks can point to arbitrary locations outside the extraction directory. This presents security risks:

8.6 Chunked File Record (Type 0x0A)

Chunked files store content in fixed-size blocks, each independently compressed. This enables random access to large compressed files without decompressing the entire file.

Structure:

[type_compression: u8]     // 0x0A | compression_method
[block_size: u32] // Size of each block before compression
[length: u64] // Total compressed length
[decompressed_length: u64] // Total original file size
[data: u64] // Byte offset to first block
[String: name]
[AttrMap: attrs]

Binary Layout:

TT type_compression
BB BB BB BB block_size (u32)
LL LL LL LL LL LL LL LL length (u64)
DD DD DD DD DD DD DD DD decompressed_length (u64)
OO OO OO OO OO OO OO OO data offset (u64)
String: name
AttrMap: attrs

Decompression:

To read bytes at logical offset O within a chunked file, use the Block FST lookup procedure described in Section 15.5.

Recommended Block Size:

A block size of 2MB (2,097,152 bytes) is RECOMMENDED. This aligns with common hugepage sizes and provides a good balance between compression ratio and random access granularity.

9 Path Encoding

Paths in Box archives use a platform-independent encoding called BoxPath.

9.1 BoxPath Format

BoxPath paths are sequences of components separated by U+001F UNIT SEPARATOR (hex \x1F).

Properties:

9.2 Separator Choice

The U+001F separator was chosen because:

  1. It forces explicit handling - developers cannot be lazy and treat paths as platform-native strings
  2. It avoids confusion with URL scheme prefixes (file://, http://) that would occur with /
  3. It is truly platform-agnostic - neither Unix nor Windows “owns” this separator
  4. It is illegal in filenames on all major platforms, preventing ambiguity

9.3 Platform Conversion

When extracting archives, implementations MUST convert BoxPath paths to platform-native paths:

Platform Separator
Unix/Linux/macOS /
Windows \

Example:

BoxPath:  foo\x1Fbar\x1Fbaz.txt
Unix: foo/bar/baz.txt
Windows: foo\bar\baz.txt

9.4 Unicode Normalization

All paths MUST be stored in NFC (Canonical Decomposition, followed by Canonical Composition) normalized form. Implementations MUST normalize paths before storage and comparison.

10 Compression

Box supports multiple compression methods, selectable per-file.

10.1 Supported Methods

Value Name Library Notes
0x00 Stored None No compression
0x10 Zstd zstd Recommended default
0x20 XZ liblzma High compression ratio

10.2 Zstd Configuration

When using Zstd:

10.3 XZ Configuration

When using XZ:

10.4 Stored (Uncompressed)

When compression is Stored (0x00):

10.5 Dictionary Usage

When a dictionary is present in BoxMetadata.dictionary (Section 7.5), it MUST be used for all Zstd-compressed content in the archive, including:

The dictionary is loaded once when opening the archive and reused for all Zstd decompression operations. The dictionary has no effect on Stored or XZ-compressed content.

11 Checksums

Box archives support content checksums for integrity verification.

11.1 Supported Algorithms

Name Attribute Output Size
Blake3 blake3 32 bytes

11.2 Checksum Storage

Checksums are stored as record attributes:

attrs["blake3"] = <32-byte hash>

The checksum is computed over the decompressed content.

11.3 Verification

Implementations SHOULD verify checksums when:

  1. Extracting files (if checksums are present)
  2. Explicitly requested by the user

Verification failures MUST be reported to the user. Implementations MAY offer options to proceed despite failures.

12 Standard Attributes

This section defines standard attribute keys that implementations SHOULD recognize.

12.1 Timestamps

Attribute Type Description
created Vi64 Creation time (minutes since Box epoch)
modified Vi64 Modification time (minutes since Box epoch)
accessed Vi64 Access time (minutes since Box epoch)

Box Epoch: 2026-01-01 00:00:00 UTC

Timestamps are OPTIONAL and stored as signed variable-length integers (Vi64, zigzag encoded) representing minutes since the Box epoch. The signed format supports dates before 2026. Minute precision is sufficient for typical archive use cases.

12.2 Sub-Minute Precision

For applications requiring higher precision:

Attribute Type Description
created.seconds U8 Additional seconds (0-59)
modified.seconds U8 Additional seconds (0-59)
accessed.seconds U8 Additional seconds (0-59)
created.nanoseconds Vu64 Sub-minute precision in nanoseconds (0-59,999,999,999)
modified.nanoseconds Vu64 Sub-minute precision in nanoseconds (0-59,999,999,999)
accessed.nanoseconds Vu64 Sub-minute precision in nanoseconds (0-59,999,999,999)

Seconds and nanoseconds attributes are OPTIONAL. If present, they provide additional precision beyond the minute-level timestamp. If nanoseconds is present, these take precedence over the seconds attribute.

12.3 Unix Permissions

Attribute Type Extraction Default
unix.mode Vu32 0o100644 (files), 0o40755 (dirs)
unix.uid Vu32 Archive attr, then current user
unix.gid Vu32 Archive attr, then current user

unix.mode Storage:

The mode value includes the file type bits in the standard Unix format:

Extraction Behavior:

If unix.mode is not present:

If unix.uid/unix.gid are not present, implementations SHOULD:

  1. Check archive-level unix.uid/unix.gid attributes
  2. Fall back to current user’s UID/GID

12.4 Attribute Encoding

Attribute values are stored as raw bytes (Vec<u8>). The type tag stored with the attribute key (see Section 7.2) determines how to interpret the value.

Standard Attribute Types:

Attribute Type Tag Encoding
created, modified, accessed DateTime (10) Zigzag-encoded i64 (minutes since Box epoch)
created.seconds, modified.seconds, accessed.seconds U8 (3) Single byte (0-59)
created.nanoseconds, modified.nanoseconds, accessed.nanoseconds Vu64 (7) FastVint-encoded u64
unix.mode, unix.uid, unix.gid Vu32 (5) FastVint-encoded u32
blake3 U256 (9) Fixed 32 bytes

Applications MAY define custom attributes with any type tag.

13 Implementation Guidance

This section provides non-normative guidance for implementers.

13.1 Memory Mapping

Box archives are designed to support memory-mapped access:

  1. Use appropriate alignment (e.g., 4096 for page alignment)
  2. Store files with compression = Stored for direct access
  3. The FST structure enables O(m) path lookups where m is path length

13.2 Streaming Creation

Archives can be created in a streaming fashion:

  1. Write header with placeholder trailer offset
  2. Write file data sequentially, recording offsets
  3. Write trailer with all records
  4. Build and write Path FST (and Block FST if needed)
  5. Seek back and update header with trailer offset

13.3 Large File Handling

For files larger than available memory:

  1. Use chunked file records for random access
  2. Stream decompression for sequential access
  3. Consider block size trade-offs (smaller = more overhead, larger = more memory)

13.4 Error Recovery

The trailer-at-end design means truncated archives lose metadata. Implementations MAY:

  1. Store periodic checkpoints during creation
  2. Implement recovery tools that scan for record boundaries
  3. Use file system features (copy-on-write, journaling) for durability

14 Path FST

The Path FST (Finite State Transducer) provides efficient path-to-record mapping.

14.1 Overview

The FST maps BoxPath strings to RecordIndex values. It supports:

14.2 FST Layout

The Path FST is located immediately after the Trailer:

trailer_end → Length (u64) 8 bytes
Header 24 bytes
Node Index node_count × 8 bytes
Hot Section Variable size (compact node headers)
Cold Section Variable size (edge data)
Table 4: FST section layout

The length prefix encodes the total size of the FST data (Header through Cold Section, not including the length prefix itself).

The Block FST (if present) uses the same structure (length prefix followed by FST data) and follows immediately after the Path FST.

14.3 FST Header

The FST Header is 24 bytes and located at the start of the FST section.

Offset Size Field Description
0x00 4 magic Magic bytes: BFST (0x42 0x46 0x53 0x54)
0x04 1 version Format version (currently 1)
0x05 1 flags Reserved flags (must be 0)
0x06 2 reserved Reserved bytes (must be 0)
0x08 4 node_count Total number of nodes (u32)
0x0C 8 entry_count Number of entries indexed (u64)
0x14 4 cold_offset Cold section start offset (u32)

The Hot Section starts at offset 24 + node_count × 8 (immediately after the Node Index). The root node is always node 0.

Implementations MUST reject FST data where magic bytes do not match or version is unsupported.

14.4 Node Index

The Node Index is an array of node_count entries, with each entry being 8 bytes.

Entry Format:

Offset Size Field Description
0x00 4 hot_offset Offset within Hot Section (u32)
0x04 4 cold_offset Offset within Cold Section (u32)

Offsets are relative to the start of their respective sections. Node IDs are indices into this array (node 0 = first entry, node 1 = second entry, etc.).

14.5 Hot Section

The Hot Section contains compact node headers optimized for cache efficiency. Each node’s hot data has the following structure:

flags: u8
edge_count: Vu64
lookup_data: variable
offsets: edge_count × u16

Flags Byte:

Bit Name Description
0 IS_FINAL Node is an accepting state (has an output value)
1 INDEXED Node uses 256-byte lookup table (17+ edges)
2-7 Reserved Must be 0

Lookup Data:

The lookup data format depends on the INDEXED flag:

Offsets Array:

Array of edge_count entries, each a u16. Each offset points to the corresponding edge’s data within this node’s cold section data block.

14.6 Cold Section

The Cold Section contains edge data and final output values. Each node’s cold data has the following structure:

For each edge (0 to edge_count-1):

label_len: Vu64
label: label_len bytes
output: Vu64
target_node: u32

If IS_FINAL flag is set:

[…] final_output: Vu64

Edge Fields:

Field Type Description
label_len Vu64 Length of edge label in bytes
label bytes Edge label (arbitrary byte sequence)
output Vu64 Output value to accumulate (u64)
target_node u32 Target node ID

Final Output:

If the IS_FINAL flag is set, the final output value is stored at the end of the node’s cold data block. This value is added to the accumulated output when the traversal terminates at this node.

14.7 Path Encoding in FST

Paths stored in the FST use the same encoding as BoxPath (Section 9):

Build Requirements:

14.8 Output Value Accumulation

The FST stores output values along edges and at final nodes. The final lookup value is computed by:

  1. Starting with accumulated value = 0
  2. For each edge traversed: accumulated = accumulated.wrapping_add(edge.output)
  3. If the final node has IS_FINAL set: accumulated = accumulated.wrapping_add(final_output)

For the Path FST, the result is a RecordIndex value. For the Block FST, the result is a physical byte offset.

14.9 FST Implementation Notes

Adaptive Node Format:

The FST uses an adaptive node format inspired by Adaptive Radix Trees (ART):

The threshold of >16 balances memory usage against lookup performance.

Hot/Cold Separation:

Data is separated into “hot” (frequently accessed during traversal) and “cold” (accessed only when needed) sections. This improves cache utilization during lookups.

SIMD Optimization:

Implementations MAY use SIMD instructions for compact node lookups:

14.10 FST Constants

These constants apply to both Path FST and Block FST:

Constant Value Description
Magic BFST FST section identifier
Version 1 Current format version
Header size 24 bytes Fixed header size
Index entry size 8 bytes Per-node index entry
Indexed threshold 17 Minimum edges for indexed format

15 Block FST

The Block FST provides logical-to-physical offset mapping for chunked files. It is located immediately after the Path FST.

Presence Detection:

Read the Path FST length prefix, then compute path_fst_end = trailer_end + sizeof(length_prefix) + path_fst_length. If path_fst_end < EOF, the Block FST begins at that position (starting with its own length prefix). If path_fst_end = EOF, no Block FST is present.

15.1 Block FST Overview

The Block FST maps (record_index, logical_offset) pairs to physical offsets within the archive’s data section. Block lookups use predecessor queries to find the block containing any arbitrary byte offset.

15.2 Block FST Format

The Block FST uses the same structure as the Path FST: a Vu64 length prefix followed by the FST data (Section 14.3Section 14.6, Section 14.8Section 14.10). Section 14.7 (Path Encoding) does not apply; Block FST uses fixed-width keys as described below.

15.3 Block FST Key Format

Keys are 16-byte fixed-width values:

Bytes 0–7: record_index (u64, big-endian)
Bytes 8–15: logical_offset (u64, big-endian)

Big-endian encoding ensures lexicographic byte order matches numeric order.

Example keys for record 5 with 2MB blocks:

Key (hex) Meaning
0000000000000005 0000000000000000 Record 5, block at offset 0
0000000000000005 0000000000200000 Record 5, block at offset 2MB
0000000000000005 0000000000400000 Record 5, block at offset 4MB

15.4 Block FST Value Format

Values are physical byte offsets (u64) pointing to the start of the compressed block data within the archive.

15.5 Block Lookup

To find the compressed data for a logical offset O in a chunked file with record index R:

  1. Construct key: [R as u64, big-endian][O as u64, big-endian]
  2. Query Block FST for predecessor (largest key ≤ query key)
  3. Verify the result key has matching record index R
  4. Result value is the physical offset of the compressed block
  5. Read and decompress the frame (compression frames are self-delimiting)
  6. The offset within the decompressed block is O - block_logical_offset where block_logical_offset is from the result key

Example: Record 5 with 2MB (2,097,152 byte) block size. Block FST contains:

[5][0] physical_offset_a
[5][2097152] physical_offset_b
[5][4194304] physical_offset_c

Query: 17KB (17,408 bytes)

  1. Construct key: [5][17408]
  2. Predecessor result: [5][0] (since 0 ≤ 17408 < 2097152)
  3. Read from physical_offset_a, decompress
  4. Offset within block: 17408 - 0 = 17408

Query: 2MB exactly (2,097,152 bytes)

  1. Construct key: [5][2097152]
  2. Predecessor result: [5][2097152] (exact match)
  3. Read from physical_offset_b, decompress
  4. Offset within block: 2097152 - 2097152 = 0

Query: 2MB + 17KB (2,114,560 bytes)

  1. Construct key: [5][2114560]
  2. Predecessor result: [5][2097152] (since 2097152 ≤ 2114560 < 4194304)
  3. Read from physical_offset_b, decompress
  4. Offset within block: 2114560 - 2097152 = 17408

15.6 Block FST Build Requirements

15.7 Streaming Decompression

Compression frames are self-delimiting, so implementations can decompress blocks without knowing their compressed size in advance. The Block FST provides the starting offset; the decompressor determines where the frame ends.

15.8 Block FST Constants

In addition to the shared constants in Section 14.10:

Constant Value Description
Key size 16 bytes Fixed: 8 bytes record index + 8 bytes logical offset