nx_zstd_frame.nx source
↩ module page · 248 lines · 8565 B
1// nx_zstd_frame.nx -- Zstandard frame and block layer, writer and reader.
2//
3// Sits above nx_zstd_fse.nx (the entropy tables). This layer is what makes a
4// .zst file walkable: magic, a variable-length frame header, then a chain of
5// blocks each with its own 3-byte header. Raw and RLE blocks decode fully
6// here -- a frame built only from those is a completely valid zstd file that
7// any zstd tool will read -- so this is a working decoder for that subset,
8// not a stub.
9//
10// COMPRESSED BLOCKS ARE REFUSED, NOT GUESSED. The literals+sequences layer is
11// not built yet, so a Compressed block returns a distinct refusal code rather
12// than emitting whatever the raw bytes happen to look like. A decoder that
13// silently passes through undecoded data is worse than one that stops.
14//
15// THE HEADER LENGTH IS COMPUTED, NOT ASSUMED. Frame_Content_Size is 0, 1, 2,
16// 4 or 8 bytes, Dictionary_ID is 0, 1, 2 or 4, and the Window_Descriptor is
17// ABSENT when Single_Segment_flag is set. Five independent choices, so the
18// header is anywhere from 2 to 14 bytes. Assuming a length works on files
19// from one encoder and fails on the next.
20//
21// THE 2-BYTE FCS IS BIASED BY 256. A two-byte Frame_Content_Size stores
22// (size - 256), so a naive read reports every such frame 256 bytes short.
23// This only affects frames between 256 and 65791 bytes, which is precisely
24// the range small test files land in.
25//
26// genealogy_id: zstandard_rfc8878_frame
27// lineage_id: nx_zstd_frame_v1
28// license_tier: ORIGINAL
29
30import "nx_syscalls.nx"
31
32const NX_ZSTD_MAGIC: i64 = 0xfd2fb528
33const NX_ZSTD_BLK_RAW: i64 = 0
34const NX_ZSTD_BLK_RLE: i64 = 1
35const NX_ZSTD_BLK_COMP: i64 = 2
36const NX_ZSTD_BLK_RESV: i64 = 3
37
38// frame field slots
39const NX_ZF_CONTENTSIZE: i64 = 0
40const NX_ZF_WINDOWSIZE: i64 = 1
41const NX_ZF_DICTID: i64 = 2
42const NX_ZF_CHECKSUM: i64 = 3
43const NX_ZF_SINGLESEG: i64 = 4
44const NX_ZF_HDRLEN: i64 = 5
45
46// block field slots
47const NX_ZB_LAST: i64 = 0
48const NX_ZB_TYPE: i64 = 1
49const NX_ZB_SIZE: i64 = 2
50const NX_ZB_DATAOFF: i64 = 3
51const NX_ZB_NEXT: i64 = 4
52
53// decode outcomes
54const NX_ZSTD_OK: i64 = 1
55const NX_ZSTD_ERR_MALFORMED: i64 = 0
56const NX_ZSTD_ERR_COMPRESSED: i64 = 0 - 2
57
58func nx_zstd_at(d: *u8, i: i64) -> i64 { return (d[i] as i64) & 255 }
59
60func nx_zstd_le(d: *u8, off: i64, n: i64) -> i64 {
61 var v: i64 = 0
62 var i: i64 = 0
63 while i < n { v = v | (nx_zstd_at(d, off + i) << (i * 8)); i = i + 1 }
64 return v
65}
66
67func nx_zstd_le_w(o: *u8, off: i64, v: i64, n: i64) -> i64 {
68 var i: i64 = 0
69 while i < n { o[off + i] = ((v >> (i * 8)) & 255) as u8; i = i + 1 }
70 return n
71}
72
73// ===== field-size tables ==========================================
74//
75// Both are irregular: neither is simply the flag value.
76
77func nx_zstd_fcs_size(flag: i64, single_segment: i64) -> i64 {
78 if flag == 0 {
79 if single_segment == 1 { return 1 }
80 return 0
81 }
82 if flag == 1 { return 2 }
83 if flag == 2 { return 4 }
84 return 8
85}
86
87func nx_zstd_did_size(flag: i64) -> i64 {
88 if flag == 0 { return 0 }
89 if flag == 1 { return 1 }
90 if flag == 2 { return 2 }
91 return 4
92}
93
94// ===== window size from the descriptor byte =======================
95
96func nx_zstd_window_size(desc: i64) -> i64 {
97 let exponent: i64 = (desc >> 3) & 31
98 let mantissa: i64 = desc & 7
99 let window_log: i64 = 10 + exponent
100 if window_log > 40 { return 0 - 1 }
101 let base: i64 = 1 << window_log
102 return base + (base / 8) * mantissa
103}
104
105// ===== frame header ===============================================
106
107func nx_zstd_frame_parse(d: *u8, n: i64, fld: *i64) -> i64 {
108 if n < 5 { return NX_ZSTD_ERR_MALFORMED }
109 if nx_zstd_le(d, 0, 4) != NX_ZSTD_MAGIC { return NX_ZSTD_ERR_MALFORMED }
110
111 let desc: i64 = nx_zstd_at(d, 4)
112 let fcs_flag: i64 = (desc >> 6) & 3
113 let single: i64 = (desc >> 5) & 1
114 let unused: i64 = (desc >> 4) & 1
115 let reserved: i64 = (desc >> 3) & 1
116 let checksum: i64 = (desc >> 2) & 1
117 let did_flag: i64 = desc & 3
118 // both must be zero: a decoder that ignores them resynchronises on junk
119 if unused != 0 { return NX_ZSTD_ERR_MALFORMED }
120 if reserved != 0 { return NX_ZSTD_ERR_MALFORMED }
121
122 var p: i64 = 5
123 var window: i64 = 0
124 if single == 0 {
125 if p >= n { return NX_ZSTD_ERR_MALFORMED }
126 window = nx_zstd_window_size(nx_zstd_at(d, p))
127 if window < 0 { return NX_ZSTD_ERR_MALFORMED }
128 p = p + 1
129 }
130
131 let did_sz: i64 = nx_zstd_did_size(did_flag)
132 if p + did_sz > n { return NX_ZSTD_ERR_MALFORMED }
133 var dictid: i64 = 0
134 if did_sz > 0 { dictid = nx_zstd_le(d, p, did_sz) }
135 p = p + did_sz
136
137 let fcs_sz: i64 = nx_zstd_fcs_size(fcs_flag, single)
138 if p + fcs_sz > n { return NX_ZSTD_ERR_MALFORMED }
139 var csize: i64 = 0 - 1
140 if fcs_sz > 0 {
141 csize = nx_zstd_le(d, p, fcs_sz)
142 // the two-byte form is biased by 256
143 if fcs_sz == 2 { csize = csize + 256 }
144 }
145 p = p + fcs_sz
146
147 // with a single segment the window IS the content size
148 if single == 1 { window = csize }
149
150 fld[NX_ZF_CONTENTSIZE] = csize
151 fld[NX_ZF_WINDOWSIZE] = window
152 fld[NX_ZF_DICTID] = dictid
153 fld[NX_ZF_CHECKSUM] = checksum
154 fld[NX_ZF_SINGLESEG] = single
155 fld[NX_ZF_HDRLEN] = p
156 return NX_ZSTD_OK
157}
158
159// writes a single-segment frame header with a 4-byte content size
160func nx_zstd_frame_write(o: *u8, cap: i64, content_size: i64) -> i64 {
161 if content_size < 0 { return 0 }
162 if cap < 9 { return 0 }
163 nx_zstd_le_w(o, 0, NX_ZSTD_MAGIC, 4)
164 // fcs_flag 2 (four bytes), single_segment set, no checksum, no dict
165 o[4] = ((2 << 6) | (1 << 5)) as u8
166 nx_zstd_le_w(o, 5, content_size, 4)
167 return 9
168}
169
170// ===== block header ===============================================
171
172func nx_zstd_block_parse(d: *u8, n: i64, off: i64, fld: *i64) -> i64 {
173 if off + 3 > n { return NX_ZSTD_ERR_MALFORMED }
174 let h: i64 = nx_zstd_le(d, off, 3)
175 let last: i64 = h & 1
176 let btype: i64 = (h >> 1) & 3
177 let bsize: i64 = h >> 3
178 if btype == NX_ZSTD_BLK_RESV { return NX_ZSTD_ERR_MALFORMED }
179
180 var stored: i64 = bsize
181 if btype == NX_ZSTD_BLK_RLE { stored = 1 }
182 if off + 3 + stored > n { return NX_ZSTD_ERR_MALFORMED }
183
184 fld[NX_ZB_LAST] = last
185 fld[NX_ZB_TYPE] = btype
186 fld[NX_ZB_SIZE] = bsize
187 fld[NX_ZB_DATAOFF] = off + 3
188 fld[NX_ZB_NEXT] = off + 3 + stored
189 return NX_ZSTD_OK
190}
191
192func nx_zstd_block_write(o: *u8, cap: i64, off: i64, last: i64, btype: i64,
193 data: *u8, size: i64) -> i64 {
194 if btype < 0 { return 0 }
195 if btype > 2 { return 0 }
196 if size < 0 { return 0 }
197 var stored: i64 = size
198 if btype == NX_ZSTD_BLK_RLE { stored = 1 }
199 if off + 3 + stored > cap { return 0 }
200 let h: i64 = (last & 1) | ((btype & 3) << 1) | (size << 3)
201 nx_zstd_le_w(o, off, h, 3)
202 var i: i64 = 0
203 while i < stored { o[off + 3 + i] = data[i]; i = i + 1 }
204 return off + 3 + stored
205}
206
207// ===== decode a whole frame =======================================
208//
209// Returns bytes written, 0 on malformed input, or NX_ZSTD_ERR_COMPRESSED if
210// the frame needs the literals+sequences layer that is not built yet.
211
212func nx_zstd_decode(d: *u8, n: i64, out: *u8, cap: i64) -> i64 {
213 let fld: *i64 = sys_mmap(128) as *i64
214 if nx_zstd_frame_parse(d, n, fld) != NX_ZSTD_OK { return NX_ZSTD_ERR_MALFORMED }
215 var p: i64 = fld[NX_ZF_HDRLEN]
216
217 let blk: *i64 = sys_mmap(128) as *i64
218 var outp: i64 = 0
219 var go: i64 = 1
220 while go == 1 {
221 let r: i64 = nx_zstd_block_parse(d, n, p, blk)
222 if r != NX_ZSTD_OK { return NX_ZSTD_ERR_MALFORMED }
223 let btype: i64 = blk[NX_ZB_TYPE]
224 let bsize: i64 = blk[NX_ZB_SIZE]
225 let doff: i64 = blk[NX_ZB_DATAOFF]
226
227 if btype == NX_ZSTD_BLK_COMP { return NX_ZSTD_ERR_COMPRESSED }
228 if outp + bsize > cap { return NX_ZSTD_ERR_MALFORMED }
229
230 if btype == NX_ZSTD_BLK_RAW {
231 var i: i64 = 0
232 while i < bsize { out[outp + i] = d[doff + i]; i = i + 1 }
233 outp = outp + bsize
234 } else {
235 let b: u8 = d[doff]
236 var i: i64 = 0
237 while i < bsize { out[outp + i] = b; i = i + 1 }
238 outp = outp + bsize
239 }
240
241 if blk[NX_ZB_LAST] == 1 { go = 0 } else { p = blk[NX_ZB_NEXT] }
242 }
243
244 // a declared content size must MATCH what the blocks produced
245 let declared: i64 = fld[NX_ZF_CONTENTSIZE]
246 if declared >= 0 { if declared != outp { return NX_ZSTD_ERR_MALFORMED } }
247 return outp
248}