nx_h2_conformance.nx source
↩ module page · 484 lines · 27484 B
1// nx_h2_conformance.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under the
2// R4-H2 HTTP/2-transport-ladder workflow), NOT credited as team self-authoring.
3//
4// R4-H2-006 of the sovereign HTTP/2 transport ladder
5// (knowledge/specs/2026-06-13-http2-transport-ladder.md). The CONFORMANCE
6// harness: it composes ALL of the lower GREEN rungs -- HPACK (R4-H2-002), the
7// frame codec (R4-H2-003), the stream state machine (R4-H2-004) and flow control
8// (R4-H2-005) -- into a minimal HTTP/2 CLIENT CONNECTION SEQUENCE asserted over
9// an IN-MEMORY / loopback byte stream (NO live network, NO sockets in the gate):
10// preface -> empty SETTINGS -> HEADERS(GET, END_STREAM|END_HEADERS)
11// -> expect peer SETTINGS + our SETTINGS-ACK exchange + response HEADERS + DATA.
12// The byte sequence is the allowed internet-boundary requirement (RFC 9113 / 7541
13// dictate the exact octets so we interoperate); the IMPLEMENTATION is pure
14// NishiLang via nx_cc -> nxasm_x86: no gcc, no openssl, no nghttp2.
15//
16// FOUNDED ON (composes, does not reinvent -- anti-orphan law):
17// - nx_h2_flow.nx (R4-H2-005, GREEN): imported EXACTLY ONCE. It transitively
18// splices nx_h2_frame.nx -> nx_hpack.nx -> nx_str.nx -> syscalls.nx, so this
19// ONE import inherits the whole stack: the HPACK encoders
20// (hpack_encode_indexed / hpack_encode_lit_inc_indexed_name), the frame
21// builders (h2_write_preface / h2_frame_write_settings_empty /
22// h2_frame_write_settings_ack / h2_frame_write_headers / h2_frame_write_data /
23// h2_frame_read_header / h2_check_preface), the flow-window arithmetic
24// (h2_flow_init / h2_flow_consume / h2_flow_is_data_frame), plus sys_mmap /
25// sys_write / sys_exit / sys_openat_append / sys_close. Importing
26// nx_h2_frame / nx_hpack / nx_str / nx_syscalls / nx_h2_stream directly TOO
27// would be the RC6 double-import landmine.
28// - nx_h2_stream.nx (R4-H2-004, GREEN): imported EXACTLY ONCE for the stream
29// state machine (h2_stream_new / h2_stream_send_headers /
30// h2_stream_recv_headers / h2_stream_recv_data). nx_h2_stream imports
31// nx_h2_frame, and nx_h2_flow ALSO imports nx_h2_frame -- but each shared
32// module is included at most once by the linker (include-guarded), so
33// importing BOTH nx_h2_flow and nx_h2_stream here pulls nx_h2_frame in once.
34// No floating capability: HPACK founds the codec founds the stream machine founds
35// flow control founds THIS conformance/ALPN rung -- the top of the ladder.
36//
37// BACK-FILL: the team RE-AUTHORS this from the DATA spec via the
38// emitter-of-emitters (X-AUT-006c/e/f) -- this hand-authored scaffold is the
39// sanctioned one-time bootstrap only (meter-integrity, mirror
40// nx_frame_codec.nx:13-16 / nx_h2_frame.nx:31-34 / nx_h2_flow.nx:30-33).
41//
42// GATE (main): asserts byte-exact the composed client opening for GET "/" to
43// authority "example.com" (preface + empty SETTINGS + HEADERS wrapping the HPACK
44// block 82 87 84 41 0b example.com), then drives the IN-MEMORY descent: a
45// synthesized server response (peer SETTINGS + ACK + HEADERS :status 200 + DATA
46// END_STREAM) is fed through h2_consume_frames, which walks each frame, dispatches
47// by type, drives the stream SM to CLOSED and debits the flow windows. PLUS
48// TAMPER cases on a SEPARATE buffer (never the canonical KAT buffer): a server
49// DATA frame injected on stream 0 (illegal -- DATA MUST NOT use stream 0x0) is
50// rejected (< 0), and the ALPN parser rejects a 2-entry EE list + a list_len that
51// disagrees with ext_data_len.
52//
53// license_tier: INDEPENDENT_REDERIVE
54// genealogy_id: international-research-sources/ietf/rfc_7301 + ietf/rfc_9113 + ietf/rfc_8446
55// lineage_id: nishi_h2_conformance_r4h2_006
56
57import "nx_h2_flow.nx"
58import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
59import "nx_h2_stream.nx"
60import "nx_tls13_ext.nx"
61const K_MAGIC_1024: i64 = 1024
62const K_MAGIC_65535: i64 = 65535
63const K_MAGIC_65533: i64 = 65533
64
65// ---- print helpers (the h2_puts/h2_putn pattern, renamed cf_*) ----
66func cf_puts(s: *u8) -> i64 {
67 var n: i64 = 0
68 while s[n] != (0 as u8) { n = n + 1 }
69 sys_write(1, s, n)
70 return 0
71}
72// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
73// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
74// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
75// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
76func cf_putn(v: i64) -> i64 { nxi_out(v); return 0 }
77func cf_puthex2(v: i64) -> i64 {
78 let h: *u8 = sys_mmap(4)
79 let d0: i64 = (v >> 4) & 0xf
80 let d1: i64 = v & 0xf
81 if d0 < 10 { h[0] = (48 + d0) as u8 } else { h[0] = (87 + d0) as u8 }
82 if d1 < 10 { h[1] = (48 + d1) as u8 } else { h[1] = (87 + d1) as u8 }
83 sys_write(1, h, 2)
84 return 0
85}
86// fd-aware decimal writer (the h2_fdn pattern) -- used ONLY by the durable
87// status-line append at the end of the gate.
88// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
89// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
90// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
91// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
92func cf_fdn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
93
94// =====================================================================
95// Build the HPACK header block for a client GET: encode the four pseudo-headers
96// :method GET (indexed 82)
97// :scheme https (indexed 87)
98// :path <path> (indexed 84 when path == "/", else literal indexed-name 4)
99// :authority <authority> (6.2.1 indexed-name, name index 1 -> lead 0x41)
100// Returns the block length, or < 0 on encode error. COMPOSES nx_hpack.
101// =====================================================================
102func h2_build_request_block(block: *u8, authority: *u8, alen: i64, path: *u8, plen: i64) -> i64 {
103 var bo: i64 = hpack_encode_indexed(block, 0, 2) // 82 :method GET
104 if bo < 0 { return bo }
105 bo = hpack_encode_indexed(block, bo, 7) // 87 :scheme https
106 if bo < 0 { return bo }
107 // :path -- "/" is static index 4 (indexed); anything else is a 6.2.2
108 // literal w/o indexing on :path (name index 4) carrying the path value.
109 if plen == 1 {
110 if (path[0] & 0xff) == 0x2f { // '/'
111 bo = hpack_encode_indexed(block, bo, 4) // 84 :path /
112 if bo < 0 { return bo }
113 }
114 if (path[0] & 0xff) != 0x2f {
115 bo = hpack_encode_lit_noindex_indexed_name(block, bo, 4, path, plen)
116 if bo < 0 { return bo }
117 }
118 }
119 if plen != 1 {
120 bo = hpack_encode_lit_noindex_indexed_name(block, bo, 4, path, plen)
121 if bo < 0 { return bo }
122 }
123 // :authority -- 6.2.1 literal w/ incremental indexing, name index 1 (0x41).
124 bo = hpack_encode_lit_inc_indexed_name(block, bo, 1, authority, alen)
125 if bo < 0 { return bo }
126 return bo
127}
128
129// =====================================================================
130// Build the minimal client opening into `out` at `off`:
131// 24-byte preface + empty client SETTINGS + HEADERS(stream 1, flags
132// END_STREAM|END_HEADERS = 0x05) wrapping the HPACK GET block.
133// Returns the total byte count (new offset), or < 0 on error. This is the
134// COMPOSITION of R4-H2-003 (preface/SETTINGS/HEADERS builders) + R4-H2-002
135// (HPACK block). The HEADERS frame transitions the R4-H2-004 stream IDLE ->
136// half-closed(local) (drven separately in the gate via the stream SM).
137// =====================================================================
138func h2_build_client_open(out: *u8, off: i64, authority: *u8, alen: i64, path: *u8, plen: i64) -> i64 {
139 var o: i64 = h2_write_preface(out, off) // +24
140 o = h2_frame_write_settings_empty(out, o) // +9 (00 00 00 04 00 00 00 00 00)
141 if o < 0 { return o }
142 let block: *u8 = sys_mmap(128)
143 let blen: i64 = h2_build_request_block(block, authority, alen, path, plen)
144 if blen < 0 { return blen }
145 o = h2_frame_write_headers(out, o, 1, 0x05, block, blen) // HEADERS stream 1, END_STREAM|END_HEADERS
146 if o < 0 { return o }
147 return o
148}
149
150// =====================================================================
151// UA-carrying request block + open (NBC/IMGSEARCH: many origins -- google image
152// search among them -- serve a JS "enable JavaScript / update your browser" wall
153// to clients that send NO user-agent; an IDENTIFIED desktop UA gets the real
154// results HTML). ADDITIVE (rule 19): same four pseudo-headers as h2_build_request_block
155// + one user-agent header (HPACK static name index 58, literal value, no indexing).
156// =====================================================================
157func h2_build_request_block_ua(block: *u8, authority: *u8, alen: i64, path: *u8, plen: i64, ua: *u8, ualen: i64) -> i64 {
158 var bo: i64 = h2_build_request_block(block, authority, alen, path, plen)
159 if bo < 0 { return bo }
160 bo = hpack_encode_lit_noindex_indexed_name(block, bo, 58, ua, ualen) // user-agent
161 if bo < 0 { return bo }
162 return bo
163}
164func h2_build_client_open_ua(out: *u8, off: i64, authority: *u8, alen: i64, path: *u8, plen: i64, ua: *u8, ualen: i64) -> i64 {
165 var o: i64 = h2_write_preface(out, off)
166 o = h2_frame_write_settings_empty(out, o)
167 if o < 0 { return o }
168 let block: *u8 = sys_mmap(K_MAGIC_1024)
169 let blen: i64 = h2_build_request_block_ua(block, authority, alen, path, plen, ua, ualen)
170 if blen < 0 { return blen }
171 o = h2_frame_write_headers(out, o, 1, 0x05, block, blen)
172 if o < 0 { return o }
173 return o
174}
175
176// =====================================================================
177// Walk a server frame stream over an in-memory buffer [off, lim). Dispatch each
178// frame by type, drive the per-stream state machine and the flow windows, and
179// REJECT illegal framing. Returns the number of frames consumed, or < 0 on a
180// protocol error. COMPOSES the codec (read-header), the stream SM (recv_*), and
181// flow control (consume / is_data_frame).
182//
183// Rejections (RFC 9113 §6.1 / §5.1):
184// - a DATA frame on stream 0 (DATA MUST NOT use stream 0x0) -> -1
185// - a header that runs past the buffer -> propagated < 0
186// - a stream-frame (HEADERS/DATA) whose SM transition is
187// illegal for the current state -> -1
188// - SETTINGS / WINDOW_UPDATE on a non-zero stream where the
189// spec requires stream 0 is NOT enforced here (connection
190// control frames are accepted on stream 0 only by checking
191// DATA/HEADERS parity); the headline illegal-stream-0 check
192// is the DATA-on-0 tamper case.
193// =====================================================================
194func h2_consume_frames(buf: *u8, off: i64, lim: i64, conn_window: *i64, stream_window: *i64, stream_state: *i64) -> i64 {
195 var o: i64 = off
196 var frames: i64 = 0
197 let rlen: *i64 = sys_mmap(16) as *i64
198 let rtype: *i64 = sys_mmap(16) as *i64
199 let rflags: *i64 = sys_mmap(16) as *i64
200 let rsid: *i64 = sys_mmap(16) as *i64
201 while o < lim {
202 let payload_start: i64 = h2_frame_read_header(buf, o, lim, rlen, rtype, rflags, rsid)
203 if payload_start < 0 { return payload_start } // truncated / over-long -> propagate
204 let ftype: i64 = rtype[0]
205 let flags: i64 = rflags[0]
206 let sid: i64 = rsid[0]
207 let plen: i64 = rlen[0]
208 let end_stream: i64 = flags & 0x01
209
210 if ftype == 0x00 { // DATA (§6.1)
211 if sid == 0 { return 0 - 1 } // DATA MUST NOT use stream 0 -> PROTOCOL_ERROR
212 // flow control: a DATA frame debits BOTH the connection and stream windows.
213 if h2_flow_is_data_frame(ftype) == 1 {
214 let cc: i64 = h2_flow_consume(conn_window, plen)
215 if cc < 0 { return cc } // connection-window overdraw
216 let sc: i64 = h2_flow_consume(stream_window, plen)
217 if sc < 0 { return sc } // stream-window overdraw
218 }
219 let ns_d: i64 = h2_stream_recv_data(stream_state[0], end_stream)
220 if ns_d < 0 { return ns_d } // illegal transition
221 stream_state[0] = ns_d
222 }
223 if ftype == 0x01 { // HEADERS (§6.2)
224 if sid == 0 { return 0 - 1 } // HEADERS MUST NOT use stream 0
225 let ns_h: i64 = h2_stream_recv_headers(stream_state[0], end_stream)
226 if ns_h < 0 { return ns_h } // illegal transition
227 stream_state[0] = ns_h
228 }
229 // SETTINGS (0x04), WINDOW_UPDATE (0x08), PING (0x06) etc. are connection
230 // control frames -- accepted on stream 0; they do not drive the stream SM
231 // and (per §5.2.1) consume zero flow window. No state change needed for
232 // the client-GET descent beyond acknowledging them as walked.
233
234 o = payload_start + plen
235 frames = frames + 1
236 }
237 return frames
238}
239
240// =====================================================================
241// GATE (main): composed byte-exact KAT + in-memory descent + tamper cases.
242// =====================================================================
243func cf_check_bytes(name: *u8, got: *u8, glen: i64, exp: *u8, elen: i64) -> i64 {
244 if glen != elen {
245 cf_puts(" FAIL " as *u8); cf_puts(name)
246 cf_puts(" length got=" as *u8); cf_putn(glen)
247 cf_puts(" exp=" as *u8); cf_putn(elen); cf_puts("\n" as *u8)
248 return 0
249 }
250 var i: i64 = 0
251 while i < glen {
252 if (got[i] & 0xff) != (exp[i] & 0xff) {
253 cf_puts(" FAIL " as *u8); cf_puts(name)
254 cf_puts(" byte[" as *u8); cf_putn(i)
255 cf_puts("] got=" as *u8); cf_puthex2(got[i] & 0xff)
256 cf_puts(" exp=" as *u8); cf_puthex2(exp[i] & 0xff); cf_puts("\n" as *u8)
257 return 0
258 }
259 i = i + 1
260 }
261 cf_puts(" PASS " as *u8); cf_puts(name)
262 cf_puts(" (" as *u8); cf_putn(glen); cf_puts(" bytes)\n" as *u8)
263 return 1
264}
265func cf_check(name: *u8, got: i64, exp: i64) -> i64 {
266 if got == exp {
267 cf_puts(" PASS " as *u8); cf_puts(name)
268 cf_puts(" (== " as *u8); cf_putn(exp); cf_puts(")\n" as *u8)
269 return 1
270 }
271 cf_puts(" FAIL " as *u8); cf_puts(name)
272 cf_puts(" got=" as *u8); cf_putn(got)
273 cf_puts(" exp=" as *u8); cf_putn(exp); cf_puts("\n" as *u8)
274 return 0
275}
276
277func main() -> i64 {
278 var pass: i64 = 0
279 var tot: i64 = 0
280 cf_puts("nx_h2_conformance gate (RFC 7301/9113/8446, FOUNDED on hpack+frame+stream+flow+ext)\n" as *u8)
281
282 let IDLE: i64 = 0
283 let HC_LOCAL: i64 = 2
284 let CLOSED: i64 = 4
285
286 // =================================================================
287 // PART A -- ALPN-h2 extension emitter byte-exact KAT (18 bytes).
288 // 00 10 00 0e 00 0c 02 68 32 08 68 74 74 70 2f 31 2e 31
289 // =================================================================
290 let aout: *u8 = sys_mmap(64)
291 let aexp: *u8 = sys_mmap(64)
292 let an: i64 = tls13_ext_emit_alpn_h2_http11(aout, 64)
293 aexp[0]=0x00 as u8; aexp[1]=0x10 as u8; aexp[2]=0x00 as u8; aexp[3]=0x0e as u8
294 aexp[4]=0x00 as u8; aexp[5]=0x0c as u8; aexp[6]=0x02 as u8; aexp[7]=0x68 as u8
295 aexp[8]=0x32 as u8; aexp[9]=0x08 as u8; aexp[10]=0x68 as u8; aexp[11]=0x74 as u8
296 aexp[12]=0x74 as u8; aexp[13]=0x70 as u8; aexp[14]=0x2f as u8; aexp[15]=0x31 as u8
297 aexp[16]=0x2e as u8; aexp[17]=0x31 as u8
298 pass = pass + cf_check_bytes("ALPN ['h2','http/1.1'] extension" as *u8, aout, an, aexp, 18); tot = tot + 1
299
300 // ---- Part A parser: EE ALPN selecting h2. ext_data (past type+len) is
301 // 00 03 02 68 32 (len 5) -> out_ptr -> offset 3 ("68 32"), out_len 2. ----
302 let ee: *u8 = sys_mmap(16)
303 ee[0]=0x00 as u8; ee[1]=0x03 as u8; ee[2]=0x02 as u8; ee[3]=0x68 as u8; ee[4]=0x32 as u8
304 let sp: *i64 = sys_mmap(16) as *i64
305 let sl: *i64 = sys_mmap(16) as *i64
306 let prc: i64 = tls13_ext_parse_alpn_selected(ee, 5, sp, sl)
307 var parse_ok: i64 = 0
308 if prc == 1 { // NX_TLS13_EXT_VERDICT_OK == 1
309 if sl[0] == 2 {
310 if (ee[sp[0]] & 0xff) == 0x68 { if (ee[sp[0] + 1] & 0xff) == 0x32 { parse_ok = 1 } }
311 }
312 }
313 if parse_ok == 1 { cf_puts(" PASS parse EE ALPN -> selected 'h2' (ptr=" as *u8); cf_putn(sp[0]); cf_puts(" len=2)\n" as *u8); pass = pass + 1 }
314 if parse_ok == 0 { cf_puts(" FAIL parse EE ALPN (rc=" as *u8); cf_putn(prc); cf_puts(" len=" as *u8); cf_putn(sl[0]); cf_puts(")\n" as *u8) }
315 tot = tot + 1
316
317 // =================================================================
318 // PART B -- composed client opening for GET "/" to authority "example.com".
319 // Canonical buffer `co` is built ONCE and NEVER mutated (tamper buffers are
320 // separate, defect-(c) discipline).
321 // =================================================================
322 let co: *u8 = sys_mmap(256)
323 let authority: *u8 = sys_mmap(32); nx_str_cpy(authority, "example.com" as *u8)
324 let path: *u8 = sys_mmap(8); nx_str_cpy(path, "/" as *u8)
325 let total: i64 = h2_build_client_open(co, 0, authority, 11, path, 1)
326
327 // Expected = preface(24) + empty SETTINGS(9) + HEADERS(9 + 16 HPACK) = 58.
328 // HPACK block (16): 82 87 84 41 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d
329 // (82 :method GET, 87 :scheme https, 84 :path /, 41 0b "example.com")
330 // HEADERS skeleton: 00 00 10 01 05 00 00 00 01 (len=0x10=16)
331 let coexp: *u8 = sys_mmap(256)
332 // preface
333 coexp[0]=0x50 as u8; coexp[1]=0x52 as u8; coexp[2]=0x49 as u8; coexp[3]=0x20 as u8
334 coexp[4]=0x2a as u8; coexp[5]=0x20 as u8; coexp[6]=0x48 as u8; coexp[7]=0x54 as u8
335 coexp[8]=0x54 as u8; coexp[9]=0x50 as u8; coexp[10]=0x2f as u8; coexp[11]=0x32 as u8
336 coexp[12]=0x2e as u8; coexp[13]=0x30 as u8; coexp[14]=0x0d as u8; coexp[15]=0x0a as u8
337 coexp[16]=0x0d as u8; coexp[17]=0x0a as u8; coexp[18]=0x53 as u8; coexp[19]=0x4d as u8
338 coexp[20]=0x0d as u8; coexp[21]=0x0a as u8; coexp[22]=0x0d as u8; coexp[23]=0x0a as u8
339 // empty SETTINGS
340 coexp[24]=0x00 as u8; coexp[25]=0x00 as u8; coexp[26]=0x00 as u8; coexp[27]=0x04 as u8
341 coexp[28]=0x00 as u8; coexp[29]=0x00 as u8; coexp[30]=0x00 as u8; coexp[31]=0x00 as u8
342 coexp[32]=0x00 as u8
343 // HEADERS skeleton (len=0x10=16, type=01, flags=05, sid=1)
344 coexp[33]=0x00 as u8; coexp[34]=0x00 as u8; coexp[35]=0x10 as u8; coexp[36]=0x01 as u8
345 coexp[37]=0x05 as u8; coexp[38]=0x00 as u8; coexp[39]=0x00 as u8; coexp[40]=0x00 as u8
346 coexp[41]=0x01 as u8
347 // HPACK block: 82 87 84 41 0b "example.com"
348 coexp[42]=0x82 as u8; coexp[43]=0x87 as u8; coexp[44]=0x84 as u8
349 coexp[45]=0x41 as u8; coexp[46]=0x0b as u8
350 coexp[47]=0x65 as u8; coexp[48]=0x78 as u8; coexp[49]=0x61 as u8; coexp[50]=0x6d as u8
351 coexp[51]=0x70 as u8; coexp[52]=0x6c as u8; coexp[53]=0x65 as u8; coexp[54]=0x2e as u8
352 coexp[55]=0x63 as u8; coexp[56]=0x6f as u8; coexp[57]=0x6d as u8
353 pass = pass + cf_check_bytes("client-open GET / example.com (preface+SETTINGS+HEADERS)" as *u8, co, total, coexp, 58); tot = tot + 1
354
355 // ---- the prefix MUST start with the 24-byte preface (h2_check_preface == 1) ----
356 pass = pass + cf_check("client-open starts with valid preface" as *u8, h2_check_preface(co, 0, 24), 1); tot = tot + 1
357
358 // ---- the request stream transitions IDLE -> HC_LOCAL on send HEADERS(END_STREAM) ----
359 var cs: i64 = h2_stream_new(1)
360 pass = pass + cf_check("stream new(1) == IDLE" as *u8, cs, IDLE); tot = tot + 1
361 cs = h2_stream_send_headers(cs, 1) // END_STREAM set -> HC_LOCAL
362 pass = pass + cf_check("send HEADERS(END_STREAM) -> HC_LOCAL" as *u8, cs, HC_LOCAL); tot = tot + 1
363
364 // =================================================================
365 // FULL IN-MEMORY DESCENT -- synthesize the server response and feed it to
366 // h2_consume_frames: peer SETTINGS + our SETTINGS-ACK + response HEADERS
367 // (:status 200 = HPACK 0x88) + DATA "OK" END_STREAM. The walk drives the
368 // stream HC_LOCAL -> CLOSED and debits the windows.
369 // =================================================================
370 let resp: *u8 = sys_mmap(256)
371 var ro: i64 = 0
372 // peer SETTINGS (empty) -- connection control, stream 0
373 ro = h2_frame_write_settings_empty(resp, ro) // +9
374 // our SETTINGS-ACK echoed in the stream (connection control, stream 0)
375 ro = h2_frame_write_settings_ack(resp, ro) // +9
376 // response HEADERS on stream 1, END_HEADERS only (no END_STREAM): HPACK :status 200
377 let hblk: *u8 = sys_mmap(16); hblk[0] = 0x88 as u8 // indexed :status 200
378 ro = h2_frame_write_headers(resp, ro, 1, 0x04, hblk, 1) // flags END_HEADERS=0x04
379 // response DATA on stream 1 with END_STREAM: body "OK"
380 let dbody: *u8 = sys_mmap(8); dbody[0]=0x4f as u8; dbody[1]=0x4b as u8 // "OK"
381 ro = h2_frame_write_data(resp, ro, 1, 0x01, dbody, 2) // END_STREAM=0x01
382
383 // windows + stream state (continuing from cs == HC_LOCAL above)
384 let connw: *i64 = sys_mmap(16) as *i64
385 let strmw: *i64 = sys_mmap(16) as *i64
386 h2_flow_init(connw) // K_MAGIC_65535
387 h2_flow_init(strmw) // K_MAGIC_65535
388 let sstate: *i64 = sys_mmap(16) as *i64
389 sstate[0] = cs // HC_LOCAL
390
391 let nframes: i64 = h2_consume_frames(resp, 0, ro, connw, strmw, sstate)
392 // 4 frames consumed: SETTINGS, SETTINGS-ACK, HEADERS, DATA.
393 pass = pass + cf_check("consume server response -> 4 frames" as *u8, nframes, 4); tot = tot + 1
394 // stream driven HC_LOCAL -> (recv HEADERS no-END stays HC_LOCAL) -> (recv DATA END_STREAM) CLOSED
395 pass = pass + cf_check("descent: stream ends CLOSED" as *u8, sstate[0], CLOSED); tot = tot + 1
396 // DATA "OK" (2 bytes) debited both windows: 65535 - 2 = 65533
397 pass = pass + cf_check("descent: conn window 65535 - 2 = 65533" as *u8, connw[0], K_MAGIC_65533); tot = tot + 1
398 pass = pass + cf_check("descent: stream window 65535 - 2 = 65533" as *u8, strmw[0], K_MAGIC_65533); tot = tot + 1
399
400 // sanity: canonical client-open buffer NOT mutated by the descent
401 pass = pass + cf_check("KAT-untouched: client-open still valid preface" as *u8, h2_check_preface(co, 0, 24), 1); tot = tot + 1
402
403 // =================================================================
404 // TAMPER CASES -- each on a SEPARATE buffer; the canonical buffers (co,
405 // resp) are NEVER mutated in place (defect-(c) discipline).
406 // =================================================================
407
408 // ---- TAMPER 1: a server DATA frame on stream 0 (illegal -- DATA MUST NOT
409 // use stream 0x0). Build it directly into a SEPARATE buffer via the raw
410 // frame-header writer (the safe builder refuses stream 0, so we craft
411 // the illegal bytes ourselves) and feed it to h2_consume_frames. ----
412 let tbuf: *u8 = sys_mmap(64)
413 // 9-byte header: len=2, type=DATA(0x00), flags=END_STREAM(0x01), stream id 0
414 var to: i64 = h2_frame_write_header(tbuf, 0, 2, 0x00, 0x01, 0)
415 tbuf[to] = 0x4f as u8; tbuf[to + 1] = 0x4b as u8 // "OK" payload
416 to = to + 2
417 let tconn: *i64 = sys_mmap(16) as *i64
418 let tstrm: *i64 = sys_mmap(16) as *i64
419 h2_flow_init(tconn); h2_flow_init(tstrm)
420 let tstate: *i64 = sys_mmap(16) as *i64; tstate[0] = HC_LOCAL
421 let data0_rc: i64 = h2_consume_frames(tbuf, 0, to, tconn, tstrm, tstate)
422 var t1ok: i64 = 0
423 if data0_rc < 0 { t1ok = 1 }
424 if t1ok == 1 { cf_puts(" PASS tamper DATA-on-stream-0 rejected (rc=" as *u8); cf_putn(data0_rc); cf_puts(")\n" as *u8); pass = pass + 1 }
425 if t1ok == 0 { cf_puts(" FAIL tamper DATA-on-stream-0 NOT rejected (rc=" as *u8); cf_putn(data0_rc); cf_puts(")\n" as *u8) }
426 tot = tot + 1
427
428 // ---- TAMPER 2: ALPN EE with TWO ProtocolName entries (RFC 7301 mandates
429 // exactly one). ext_data = 00 06 02 68 32 02 68 32 (list_len 6, two
430 // "h2" entries) -> the single-entry parser MUST reject (1 + name_len !=
431 // list_len because the first entry only consumes 3 of 6). ----
432 let ee2: *u8 = sys_mmap(16)
433 ee2[0]=0x00 as u8; ee2[1]=0x06 as u8 // list_len = 6
434 ee2[2]=0x02 as u8; ee2[3]=0x68 as u8; ee2[4]=0x32 as u8 // entry 1: "h2"
435 ee2[5]=0x02 as u8; ee2[6]=0x68 as u8; ee2[7]=0x32 as u8 // entry 2: "h2" (illegal)
436 let sp2: *i64 = sys_mmap(16) as *i64
437 let sl2: *i64 = sys_mmap(16) as *i64
438 let two_rc: i64 = tls13_ext_parse_alpn_selected(ee2, 8, sp2, sl2)
439 var t2ok: i64 = 0
440 if two_rc < 0 { t2ok = 1 }
441 if t2ok == 1 { cf_puts(" PASS tamper ALPN-two-entries rejected (rc=" as *u8); cf_putn(two_rc); cf_puts(")\n" as *u8); pass = pass + 1 }
442 if t2ok == 0 { cf_puts(" FAIL tamper ALPN-two-entries NOT rejected (rc=" as *u8); cf_putn(two_rc); cf_puts(")\n" as *u8) }
443 tot = tot + 1
444
445 // ---- TAMPER 3: ALPN EE list_len != ext_data_len - 2 (framing mismatch).
446 // ext_data = 00 09 02 68 32 (claims list_len 9 but only 3 bytes follow,
447 // ext_data_len 5) -> rejected. ----
448 let ee3: *u8 = sys_mmap(16)
449 ee3[0]=0x00 as u8; ee3[1]=0x09 as u8; ee3[2]=0x02 as u8; ee3[3]=0x68 as u8; ee3[4]=0x32 as u8
450 let sp3: *i64 = sys_mmap(16) as *i64
451 let sl3: *i64 = sys_mmap(16) as *i64
452 let mism_rc: i64 = tls13_ext_parse_alpn_selected(ee3, 5, sp3, sl3)
453 var t3ok: i64 = 0
454 if mism_rc < 0 { t3ok = 1 }
455 if t3ok == 1 { cf_puts(" PASS tamper ALPN-list_len-mismatch rejected (rc=" as *u8); cf_putn(mism_rc); cf_puts(")\n" as *u8); pass = pass + 1 }
456 if t3ok == 0 { cf_puts(" FAIL tamper ALPN-list_len-mismatch NOT rejected (rc=" as *u8); cf_putn(mism_rc); cf_puts(")\n" as *u8) }
457 tot = tot + 1
458
459 cf_puts("---- h2_conformance gate: passed " as *u8); cf_putn(pass); cf_puts(" / " as *u8); cf_putn(tot); cf_puts("\n" as *u8)
460 if pass == tot {
461 // DURABLE EVIDENCE (mirror nx_h2_frame.nx:586 / nx_h2_flow.nx:416):
462 // stdout evaporates and cannot anchor a row_markers entry -- append ONE
463 // marker/reconcile-recognised status line. Reached ONLY when every KAT
464 // + all three tamper cases pass (pass == tot).
465 let lfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_conformance.log" as *u8, 0x1a4)
466 if lfd >= 0 {
467 sys_write(lfd, "R4-H2-006-GATE organ=nx_h2_conformance kats=" as *u8, 44)
468 cf_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); cf_fdn(lfd, tot)
469 sys_write(lfd, " tamper=ok verdict=GREEN\n" as *u8, 25)
470 sys_close(lfd)
471 }
472 sys_exit(0)
473 }
474 // RED path: still write a durable line so the failure is anchored, then exit 1.
475 let rfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_conformance.log" as *u8, 0x1a4)
476 if rfd >= 0 {
477 sys_write(rfd, "R4-H2-006-GATE organ=nx_h2_conformance kats=" as *u8, 44)
478 cf_fdn(rfd, pass); sys_write(rfd, "/" as *u8, 1); cf_fdn(rfd, tot)
479 sys_write(rfd, " tamper=?? verdict=RED\n" as *u8, 23)
480 sys_close(rfd)
481 }
482 sys_exit(1)
483 return 0
484}