lz4_header.nx source
↩ module page · 161 lines · 5533 B
1// lz4_header.nx -- parse LZ4 frame format headers (RFC-less
2// spec: https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md).
3//
4// Parses and validates the fixed magic + FLG + BD bytes that
5// precede every LZ4-framed stream. Block decompression (the
6// actual LZ4 algorithm) is a much larger port and ships in a
7// future lz4.nx; this module is enough to:
8// - detect whether a blob is an LZ4 frame
9// - read the block-size code + content-checksum flag
10// - know how many header bytes to skip before the first block
11//
12// Magic: 0x184D2294 little-endian (4 bytes).
13//
14// FLG byte:
15// bit 7-6: version (must be 01 for v1)
16// bit 5: block independence (1 = independent, 0 = linked)
17// bit 4: block checksum present
18// bit 3: content size present (if set, 8 bytes follow BD)
19// bit 2: content checksum present (xxHash32 at end of stream)
20// bit 1: reserved (must be 0)
21// bit 0: dictionary ID present (if set, 4 bytes follow)
22//
23// BD byte:
24// bit 7: reserved
25// bit 6-4: block max size (4=64KB, 5=256KB, 6=1MB, 7=4MB)
26// bit 3-0: reserved
27//
28// Then 1 byte HC (header checksum, low-byte of xxHash32 of
29// (FLG, BD, [content_size], [dict_id])).
30//
31// Invariants:
32// L1 Magic match required; otherwise LZ4_ERR_FORMAT.
33// L2 FLG version must be 01.
34// L3 Header length varies (7, 11, 15, or 19 bytes).
35
36import "syscalls.nx"
37
38const LZ4_MAGIC_0: i64 = 0x04
39const LZ4_MAGIC_1: i64 = 0x22
40const LZ4_MAGIC_2: i64 = 0x4D
41const LZ4_MAGIC_3: i64 = 0x18
42
43const LZ4_ERR_FORMAT: i64 = -1
44const LZ4_ERR_VERSION: i64 = -2
45const LZ4_ERR_SHORT: i64 = -3
46
47struct Lz4Header {
48 // Parsed FLG fields.
49 version: i64,
50 block_independent: i64,
51 block_checksum: i64,
52 content_size_flag: i64,
53 content_checksum: i64,
54 dict_id_flag: i64,
55 // Parsed BD fields.
56 block_max_code: i64, // 4..7
57 // Optional fields (zero if flag not set).
58 content_size: i64,
59 dict_id: i64,
60 // Total header length in bytes (magic + FLG + BD + optionals + HC).
61 header_len: i64,
62}
63
64// Read a little-endian u32 at offset.
65func lz4_read_u32(buf: *u8, off: i64) -> i64 {
66 let b0: i64 = buf[off]
67 let b1: i64 = buf[off + 1]
68 let b2: i64 = buf[off + 2]
69 let b3: i64 = buf[off + 3]
70 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
71}
72
73// Read a little-endian u64 at offset.
74func lz4_read_u64(buf: *u8, off: i64) -> i64 {
75 let lo: i64 = lz4_read_u32(buf, off)
76 let hi: i64 = lz4_read_u32(buf, off + 4)
77 return lo | (hi << 32)
78}
79
80// Parse an LZ4 frame header. Returns 0 on success with h filled,
81// or a negative LZ4_ERR_* on failure. Caller supplies at least
82// 7 bytes (minimum header).
83func lz4_parse_header(buf: *u8, n: i64, h: *Lz4Header) -> i64 {
84 if n < 7 { return LZ4_ERR_SHORT }
85 if buf[0] != LZ4_MAGIC_0 { return LZ4_ERR_FORMAT }
86 if buf[1] != LZ4_MAGIC_1 { return LZ4_ERR_FORMAT }
87 if buf[2] != LZ4_MAGIC_2 { return LZ4_ERR_FORMAT }
88 if buf[3] != LZ4_MAGIC_3 { return LZ4_ERR_FORMAT }
89
90 let flg: i64 = buf[4]
91 let version: i64 = (flg >> 6) & 0x3
92 if version != 1 { return LZ4_ERR_VERSION }
93
94 h.version = version
95 h.block_independent = (flg >> 5) & 0x1
96 h.block_checksum = (flg >> 4) & 0x1
97 h.content_size_flag = (flg >> 3) & 0x1
98 h.content_checksum = (flg >> 2) & 0x1
99 h.dict_id_flag = flg & 0x1
100
101 let bd: i64 = buf[5]
102 h.block_max_code = (bd >> 4) & 0x7
103 h.content_size = 0
104 h.dict_id = 0
105
106 // Minimum header: magic(4) + FLG(1) + BD(1) + HC(1) = 7.
107 var off: i64 = 6
108 if h.content_size_flag == 1 {
109 if n < off + 8 + 1 { return LZ4_ERR_SHORT }
110 h.content_size = lz4_read_u64(buf, off)
111 off = off + 8
112 }
113 if h.dict_id_flag == 1 {
114 if n < off + 4 + 1 { return LZ4_ERR_SHORT }
115 h.dict_id = lz4_read_u32(buf, off)
116 off = off + 4
117 }
118 // HC byte follows; we don't verify (needs xxHash32).
119 off = off + 1
120 h.header_len = off
121 return 0
122}
123
124// Translate block_max_code (4..7) to bytes: 64KB, 256KB, 1MB, 4MB.
125// Returns -1 for invalid codes (0..3 are reserved).
126func lz4_block_max_bytes(code: i64) -> i64 {
127 if code == 4 { return 65536 }
128 if code == 5 { return 262144 }
129 if code == 6 { return 1048576 }
130 if code == 7 { return 4194304 }
131 return -1
132}
133
134// Compile-only smoke -- forge a minimal valid frame header in a
135// buffer and parse it back.
136func main() -> i64 {
137 let raw: *u8 = sys_mmap(64)
138 // Minimum header: no content-size, no dict-id, v1, block-max=4 (64KB).
139 raw[0] = 0x04; raw[1] = 0x22; raw[2] = 0x4D; raw[3] = 0x18
140 // FLG: version=01 (bit 7-6), rest zero -> 0x40.
141 raw[4] = 0x40
142 // BD: block-max=4 in bits 6-4 -> (4 << 4) = 0x40.
143 raw[5] = 0x40
144 // HC: any byte (we don't verify).
145 raw[6] = 0x00
146
147 let h_raw: *u8 = sys_mmap(128)
148 let h: *Lz4Header = h_raw as *Lz4Header
149 if lz4_parse_header(raw, 7, h) != 0 { return 1 }
150 if h.version != 1 { return 2 }
151 if h.block_max_code != 4 { return 3 }
152 if h.header_len != 7 { return 4 }
153 if lz4_block_max_bytes(4) != 65536 { return 5 }
154 if lz4_block_max_bytes(7) != 4194304 { return 6 }
155 if lz4_block_max_bytes(0) != -1 { return 7 }
156
157 // Wrong magic => LZ4_ERR_FORMAT.
158 raw[0] = 0x00
159 if lz4_parse_header(raw, 7, h) != LZ4_ERR_FORMAT { return 8 }
160 return 0
161}