code wiki / (root) / lz4_header.nx

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}