nx_codec_caps.nx source
↩ module page · 279 lines · 10356 B
1// nx_codec_caps.nx -- the SINGLE source of truth for what we may advertise.
2//
3// THE DEFECT THIS EXISTS TO KILL. Thirteen call sites across the fetchers and
4// the browser each hand-wrote their own negotiation headers. Two of them lie:
5// nx_http_client sends `Accept: ...,image/avif,image/webp,...` and
6// nx_polite_browser sends `Accept-Encoding: gzip, deflate, br`. We have no
7// AVIF pixel codec and no Brotli decoder. A server that believes either one
8// answers with bytes we cannot read -- and the failure is not an error, it is
9// a blank image or a body of binary noise handed to an HTML parser.
10//
11// The other nine sites have the opposite defect: they send
12// `Accept-Encoding: identity` while nx_gzip_inflate and nx_zlib_inflate are
13// both in-tree and complete. That is a real capability left on the floor on
14// every fetch.
15//
16// A FIX THAT NEEDS MEMORY AT THIRTEEN SITES IS NOT A FIX. So the header is not
17// written anywhere -- it is DERIVED, here, from a registry of what actually
18// decodes. Adding a decoder means flipping one flag in one table and every
19// call site that composes this becomes honest in the same act. Drift is not
20// discouraged, it is unrepresentable.
21//
22// WHAT IS DECLARED HAVE, AND WHY (verified 2026-07-31 by reading the tree,
23// not by recalling it):
24// image/gif -- nx_gif_decode.nx (gif_decode -> w*h grayscale, LZW, + gate)
25// WIRED into nx_img_bytes_to_rgb 2026-08-04; before that the
26// decoder existed but no image path called it.
27//
28// WITHDRAWN 2026-08-04 -- image/webp, and WHY (measured, not recalled):
29// The row above claimed "VP8L lossless + VP8 keyframe". VP8L is real. VP8
30// KEYFRAME IS NOT: nx_vp8.nx is the boolean entropy decoder + frame-header
31// parser ONLY -- no macroblock modes, no coefficient decode, no dequant/IDCT,
32// no intra prediction, no reconstruction. And webp_decode() walks chunks to
33// the VP8L fourcc and returns 0 for anything else.
34// MEASURED (nx_webpprobe over a 101-file real-web corpus): 25 of 25 WebP were
35// VP8 LOSSY, and webp_decode returned 0 on every one. Zero VP8L in the wild.
36// CONSEQUENCE OF THE LIE: because we advertised image/webp, content-negotiating
37// origins served WebP for URLs ending .jpg (proven: a Wikimedia
38// 250px-*.jpg came back RIFF/WEBP), so the advertisement CREATED the
39// undecodable corpus it claimed to be able to read.
40// Restore have=1 only when a VP8 keyframe decoder reconstructs real pixels.
41// image/png -- nx_png_decoder.nx
42// image/jpeg -- nx_jpeg_decode_image.nx (baseline + progressive)
43// gzip -- nx_gzip_inflate (nx_gzip_wrap.nx) over nx_deflate + nx_crc32
44// deflate -- nx_zlib_inflate (nx_zlib_wrap.nx), i.e. RFC 1950 zlib,
45// which is what HTTP `deflate` actually means
46//
47// WHAT IS DECLARED ABSENT, AND WHY:
48// image/avif -- eight AV1 layers exist (entropy coder, OBU, sequence, tile,
49// frame, transforms, DCT, intra) but no frame decoder yet
50// image/apng -- nx_png_decoder handles a single IDAT stream, not acTL/fcTL
51// br -- no Brotli decoder; the 122KB static dictionary is unbuilt
52// zstd -- ten gated modules exist but no real .zst has been decoded
53// end to end, so the bytes are unproven. An UNPROVEN decoder
54// must not appear in a header: advertising it is a promise.
55//
56// license_tier: ORIGINAL
57import "nx_syscalls.nx"
58const NX_MAGIC_1024: i64 = 1024
59
60// ===== registry: image formats ====================================
61
62const NX_CC_N_IMAGE: i64 = 8
63
64func cc_image_name(i: i64) -> *u8 {
65 if i == 0 { return "image/webp" as *u8 }
66 if i == 1 { return "image/png" as *u8 }
67 if i == 2 { return "image/jpeg" as *u8 }
68 if i == 3 { return "image/gif" as *u8 }
69 if i == 4 { return "image/avif" as *u8 }
70 if i == 5 { return "image/apng" as *u8 }
71 if i == 6 { return "image/bmp" as *u8 }
72 if i == 7 { return "image/tiff" as *u8 }
73 return 0 as *u8
74}
75
76func cc_image_have(i: i64) -> i64 {
77 if i == 0 { return 0 } // image/webp: LOSSY VP8 IS NOT DECODABLE -- see the note above (2026-08-04)
78 if i == 1 { return 1 }
79 if i == 2 { return 1 } // + DRI/RST restart markers 2026-08-05 (nx_jpeg_rst_gate)
80 if i == 3 { return 1 } // + palette colour + de-interlace 2026-08-05 (nx_gif_color_gate)
81 if i == 4 { return 0 }
82 if i == 5 { return 0 }
83 if i == 6 { return 1 } // image/bmp: nx_bmp_decode 2026-08-05 (nx_bmp_tiff_gate) -- BI_RGB 1/4/8/24/32 + RLE8
84 if i == 7 { return 1 } // image/tiff: nx_tiff_decode 2026-08-05 (nx_bmp_tiff_gate) -- baseline strips, none+PackBits
85 return 0
86}
87
88// ===== registry: content encodings ================================
89//
90// `identity` is not in the table -- it is the floor, always available, and
91// emitted when nothing else is. Putting it in the table would let a future
92// edit set have=0 on it and produce an empty header.
93
94const NX_CC_N_ENCODING: i64 = 4
95
96func cc_encoding_name(i: i64) -> *u8 {
97 if i == 0 { return "gzip" as *u8 }
98 if i == 1 { return "deflate" as *u8 }
99 if i == 2 { return "br" as *u8 }
100 if i == 3 { return "zstd" as *u8 }
101 return 0 as *u8
102}
103
104func cc_encoding_have(i: i64) -> i64 {
105 if i == 0 { return 1 }
106 if i == 1 { return 1 }
107 if i == 2 { return 0 }
108 if i == 3 { return 0 }
109 return 0
110}
111
112// ===== small string helpers =======================================
113
114func nx_cc_len(s: *u8) -> i64 {
115 var n: i64 = 0
116 if s == (0 as *u8) { return 0 }
117 while s[n] != (0 as u8) { n = n + 1 }
118 return n
119}
120
121func nx_cc_eq(a: *u8, b: *u8) -> i64 {
122 var i: i64 = 0
123 if a == (0 as *u8) { return 0 }
124 if b == (0 as *u8) { return 0 }
125 while 1 == 1 {
126 if a[i] != b[i] { return 0 }
127 if a[i] == (0 as u8) { return 1 }
128 i = i + 1
129 }
130 return 0
131}
132
133// Append s at out[off]; returns the new offset. Always leaves out
134// NUL-terminated so a partially built header is still a valid C string.
135func nx_cc_put(out: *u8, off: i64, s: *u8) -> i64 {
136 var i: i64 = 0
137 var o: i64 = off
138 if s == (0 as *u8) { return off }
139 while s[i] != (0 as u8) {
140 out[o] = s[i]
141 o = o + 1
142 i = i + 1
143 }
144 out[o] = 0 as u8
145 return o
146}
147
148// Index of needle in hay, or -1. Used by callers that must assert a
149// header does NOT carry a token.
150func nx_cc_find(hay: *u8, needle: *u8) -> i64 {
151 let hn: i64 = nx_cc_len(hay)
152 let nn: i64 = nx_cc_len(needle)
153 var i: i64 = 0
154 var j: i64 = 0
155 var hit: i64 = 0
156 if nn == 0 { return 0 }
157 if nn > hn { return 0 - 1 }
158 while i <= hn - nn {
159 j = 0
160 hit = 1
161 while j < nn {
162 if hay[i + j] != needle[j] { hit = 0; j = nn } else { j = j + 1 }
163 }
164 if hit == 1 { return i }
165 i = i + 1
166 }
167 return 0 - 1
168}
169
170// Count comma-separated tokens in s. Empty string is zero tokens.
171// The gate uses this to prove the emitted header carries EXACTLY as
172// many entries as the registry declares available -- a token count
173// that drifts from the registry means something was hand-added.
174func nx_cc_count_tokens(s: *u8) -> i64 {
175 let n: i64 = nx_cc_len(s)
176 var i: i64 = 0
177 var count: i64 = 1
178 if n == 0 { return 0 }
179 while i < n {
180 if s[i] == (0x2c as u8) { count = count + 1 }
181 i = i + 1
182 }
183 return count
184}
185
186// ===== capability queries =========================================
187//
188// An unknown name returns 0. A wrong answer here would put a format we
189// cannot decode into a live header, which is the exact defect.
190
191func nx_codec_caps_have_image(name: *u8) -> i64 {
192 var i: i64 = 0
193 while i < NX_CC_N_IMAGE {
194 if nx_cc_eq(cc_image_name(i), name) == 1 { return cc_image_have(i) }
195 i = i + 1
196 }
197 return 0
198}
199
200func nx_codec_caps_have_encoding(name: *u8) -> i64 {
201 var i: i64 = 0
202 if nx_cc_eq("identity" as *u8, name) == 1 { return 1 }
203 while i < NX_CC_N_ENCODING {
204 if nx_cc_eq(cc_encoding_name(i), name) == 1 { return cc_encoding_have(i) }
205 i = i + 1
206 }
207 return 0
208}
209
210// ===== header emitters ============================================
211//
212// Both walk the registry. Neither contains a literal format name, so
213// neither can disagree with the queries above.
214
215func nx_codec_caps_accept_image(out: *u8) -> i64 {
216 var i: i64 = 0
217 var o: i64 = 0
218 var first: i64 = 1
219 out[0] = 0 as u8
220 while i < NX_CC_N_IMAGE {
221 if cc_image_have(i) == 1 {
222 if first == 0 { o = nx_cc_put(out, o, "," as *u8) }
223 o = nx_cc_put(out, o, cc_image_name(i))
224 first = 0
225 }
226 i = i + 1
227 }
228 return o
229}
230
231// Never empty: with no decoders at all this still emits `identity`,
232// which is the one encoding that is true by construction.
233func nx_codec_caps_accept_encoding(out: *u8) -> i64 {
234 var i: i64 = 0
235 var o: i64 = 0
236 var first: i64 = 1
237 out[0] = 0 as u8
238 while i < NX_CC_N_ENCODING {
239 if cc_encoding_have(i) == 1 {
240 if first == 0 { o = nx_cc_put(out, o, ", " as *u8) }
241 o = nx_cc_put(out, o, cc_encoding_name(i))
242 first = 0
243 }
244 i = i + 1
245 }
246 if first == 1 { o = nx_cc_put(out, o, "identity" as *u8) }
247 return o
248}
249
250// The full Accept value for a document fetch. The image sublist is the
251// SAME string accept_image produces -- spliced, not re-typed -- so the
252// document and image negotiations cannot drift apart.
253func nx_codec_caps_accept_document(out: *u8) -> i64 {
254 let imgs: *u8 = sys_mmap(512)
255 var o: i64 = 0
256 nx_codec_caps_accept_image(imgs)
257 out[0] = 0 as u8
258 o = nx_cc_put(out, o, "text/html,application/xhtml+xml,application/xml;q=0.9," as *u8)
259 o = nx_cc_put(out, o, imgs)
260 o = nx_cc_put(out, o, ",*/*;q=0.8" as *u8)
261 return o
262}
263
264// The complete request-header block a fetcher should send. One call,
265// so a new fetcher cannot forget half of it.
266func nx_codec_caps_headers(out: *u8) -> i64 {
267 let acc: *u8 = sys_mmap(NX_MAGIC_1024)
268 let enc: *u8 = sys_mmap(256)
269 var o: i64 = 0
270 nx_codec_caps_accept_document(acc)
271 nx_codec_caps_accept_encoding(enc)
272 out[0] = 0 as u8
273 o = nx_cc_put(out, o, "Accept: " as *u8)
274 o = nx_cc_put(out, o, acc)
275 o = nx_cc_put(out, o, "\r\nAccept-Encoding: " as *u8)
276 o = nx_cc_put(out, o, enc)
277 o = nx_cc_put(out, o, "\r\n" as *u8)
278 return o
279}