nx_h2_frame.nx source
↩ module page · 583 lines · 31682 B
1// nx_h2_frame.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under the R4-H2
2// HTTP/2-transport-ladder workflow), NOT credited as team self-authoring.
3//
4// R4-H2-003 of the sovereign HTTP/2 transport ladder
5// (knowledge/specs/2026-06-13-http2-transport-ladder.md). The HTTP/2 frame
6// codec (RFC 9113 §3.4 / §4.1 / §6). The WIRE FORMAT -- the 9-octet generic
7// frame header (Length 24-bit BE, Type 8, Flags 8, R 1 + Stream-ID 31 BE), the
8// 24-octet connection preface, and the per-frame payload layouts of SETTINGS,
9// HEADERS, DATA, RST_STREAM, PING, GOAWAY, WINDOW_UPDATE -- is an allowed
10// internet-boundary requirement: the bytes are dictated by RFC 9113 byte-for-
11// byte so we interoperate, exactly as nx_tls13_ext.nx emits RFC 8446 extension
12// bytes and nx_hpack.nx emits RFC 7541 header blocks. The IMPLEMENTATION is
13// pure NishiLang via nx_cc -> nxasm_x86: no gcc, no openssl, no nghttp2.
14//
15// FOUNDED ON (composes, does not reinvent -- anti-orphan law):
16// - nx_hpack.nx (R4-H2-002, GREEN): the HEADERS frame wraps an HPACK block
17// built by hpack_encode_indexed / hpack_encode_lit_inc_indexed_name; it
18// transitively splices nx_str.nx -> syscalls.nx -> nx_syscalls.nx, so we
19// inherit sys_mmap / sys_write / sys_exit from ONE import (importing
20// nx_str / nx_syscalls directly TOO would be the RC6 double-import landmine).
21// - nx_tls13.nx (no imports; pure byte helpers): we reuse tls_write_u16_be /
22// tls_read_u16_be (nx_tls13.nx:201/205) for the SETTINGS Identifier(16) AND
23// tls_write_u24_be / tls_read_u24_be (nx_tls13.nx:188/192) for the 24-bit
24// frame Length -- the exact same big-endian helpers the TLS handshake-message
25// headers use, NOT reinvented here (Rule-15 DRY). Only the u31/u32 BE pair
26// (which has no TLS counterpart) is added locally. nx_tls13 imports nothing,
27// so importing it alongside nx_hpack does NOT double-import syscalls.
28// No floating capability: HPACK founds this frame codec; the stream state
29// machine (R4-H2-004) founds on this next.
30//
31// BACK-FILL: the team RE-AUTHORS this from the DATA spec via the
32// emitter-of-emitters (X-AUT-006c/e/f) -- this hand-authored scaffold is the
33// sanctioned one-time bootstrap only (meter-integrity, mirror
34// nx_frame_codec.nx:13-16 / nx_hpack.nx:28-31).
35//
36// GATE (main): asserts byte-exact the RFC 9113 known-answers -- the 24-byte
37// connection preface (§3.4), the empty client SETTINGS frame (00 00 00 04 00 00
38// 00 00 00), SETTINGS ACK, a SETTINGS frame carrying INITIAL_WINDOW_SIZE=65535,
39// a HEADERS frame on stream 1 (flags END_STREAM|END_HEADERS = 0x05) wrapping the
40// C.3.1-style HPACK block, plus WINDOW_UPDATE / RST_STREAM / PING / GOAWAY /
41// DATA builders, the read-header round-trip, and h2_check_preface == 1 -- PLUS
42// TAMPER cases: a corrupted preface byte (h2_check_preface -> 0), a SETTINGS
43// length that is not a multiple of 6 (rejected), an RST_STREAM declared with a
44// non-4 payload (rejected), and a declared frame length that exceeds the buffer
45// (h2_frame_read_header -> negative).
46//
47// license_tier: INDEPENDENT_REDERIVE
48// genealogy_id: international-research-sources/ietf/rfc_9113
49// lineage_id: nishi_h2_frame_r4h2_003
50
51import "nx_hpack.nx"
52import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
53import "nx_tls13.nx"
54const K_MAGIC_65535: i64 = 65535
55
56// ---- print helpers (the fc_puts/fc_putn pattern, renamed per organ) ----
57func h2_puts(s: *u8) -> i64 {
58 var n: i64 = 0
59 while s[n] != (0 as u8) { n = n + 1 }
60 sys_write(1, s, n)
61 return 0
62}
63// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
64// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
65// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
66// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
67func h2_putn(v: i64) -> i64 { nxi_out(v); return 0 }
68func h2_puthex2(v: i64) -> i64 {
69 let h: *u8 = sys_mmap(4)
70 let d0: i64 = (v >> 4) & 0xf
71 let d1: i64 = v & 0xf
72 if d0 < 10 { h[0] = (48 + d0) as u8 } else { h[0] = (87 + d0) as u8 }
73 if d1 < 10 { h[1] = (48 + d1) as u8 } else { h[1] = (87 + d1) as u8 }
74 sys_write(1, h, 2)
75 return 0
76}
77// fd-aware decimal writer (the hp_fdn pattern from nx_hpack.nx:70) -- used ONLY
78// by the durable status-line append at the end of the gate.
79// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
80// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
81// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
82// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
83func h2_fdn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
84
85// =====================================================================
86// Big-endian width helpers. u16 = tls_write_u16_be / tls_read_u16_be and
87// u24 = tls_write_u24_be / tls_read_u24_be -- BOTH composed from nx_tls13.nx
88// (the same helpers the TLS handshake headers use), NOT reinvented here
89// (Rule-15 DRY; defect (b) from the prior council). Only the u32 pair below
90// (no TLS counterpart) is local.
91// =====================================================================
92func h2_write_u32_be(buf: *u8, off: i64, v: i64) -> i64 {
93 buf[off] = ((v >> 24) & 0xff) as u8
94 buf[off + 1] = ((v >> 16) & 0xff) as u8
95 buf[off + 2] = ((v >> 8) & 0xff) as u8
96 buf[off + 3] = ( v & 0xff) as u8
97 return 4
98}
99func h2_read_u32_be(buf: *u8, off: i64) -> i64 {
100 return ((buf[off] & 0xff) << 24) | ((buf[off + 1] & 0xff) << 16) | ((buf[off + 2] & 0xff) << 8) | (buf[off + 3] & 0xff)
101}
102
103// =====================================================================
104// §4.1 Generic 9-octet frame header.
105// Length(24) | Type(8) | Flags(8) | R(1)=0 + StreamID(31)
106// Length counts the PAYLOAD ONLY (off-by-9 is the classic bug). R is the MSB
107// of the 4-byte stream-id field; we write 0 and mask `id & 0x7FFFFFFF`.
108// =====================================================================
109func h2_frame_write_header(out: *u8, off: i64, length: i64, ftype: i64, flags: i64, stream_id: i64) -> i64 {
110 if length < 0 { return 0 - 1 }
111 if ftype < 0 { return 0 - 1 }
112 if ftype > 255 { return 0 - 1 }
113 var o: i64 = off
114 tls_write_u24_be(out, o, length); o = o + 3 // DRY: reuse the TLS u24 BE helper
115 out[o] = (ftype & 0xff) as u8; o = o + 1
116 out[o] = (flags & 0xff) as u8; o = o + 1
117 // 4-byte stream-id with the R (reserved) high bit forced to 0.
118 h2_write_u32_be(out, o, stream_id & 0x7fffffff); o = o + 4
119 return o
120}
121
122// Parse a 9-octet header at off. `lim` is one-past-the-last readable octet.
123// Writes (len,type,flags,sid) and returns the offset of the FIRST PAYLOAD octet
124// (off+9), or < 0: -1 = header truncated (fewer than 9 octets before lim),
125// -2 = declared Length runs past lim (the payload would exceed the buffer).
126func h2_frame_read_header(buf: *u8, off: i64, lim: i64, out_len: *i64, out_type: *i64, out_flags: *i64, out_sid: *i64) -> i64 {
127 if off + 9 > lim { return 0 - 1 } // header itself truncated
128 let length: i64 = tls_read_u24_be(buf, off) // DRY: reuse the TLS u24 BE helper
129 let ftype: i64 = buf[off + 3] & 0xff
130 let flags: i64 = buf[off + 4] & 0xff
131 let sid: i64 = h2_read_u32_be(buf, off + 4 + 1) & 0x7fffffff
132 let payload_start: i64 = off + 9
133 if payload_start + length > lim { return 0 - 2 } // declared length exceeds buffer -> reject (tamper)
134 *out_len = length
135 *out_type = ftype
136 *out_flags = flags
137 *out_sid = sid
138 return payload_start
139}
140
141// =====================================================================
142// §3.4 Client connection preface -- 24 octets, the ASCII string
143// "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", MUST be immediately followed by SETTINGS.
144// =====================================================================
145// 50 52 49 20 2a 20 48 54 54 50 2f 32 2e 30 0d 0a 0d 0a 53 4d 0d 0a 0d 0a
146func h2_write_preface(out: *u8, off: i64) -> i64 {
147 var o: i64 = off
148 out[o] = 0x50 as u8; o = o + 1 // 'P'
149 out[o] = 0x52 as u8; o = o + 1 // 'R'
150 out[o] = 0x49 as u8; o = o + 1 // 'I'
151 out[o] = 0x20 as u8; o = o + 1 // ' '
152 out[o] = 0x2a as u8; o = o + 1 // '*'
153 out[o] = 0x20 as u8; o = o + 1 // ' '
154 out[o] = 0x48 as u8; o = o + 1 // 'H'
155 out[o] = 0x54 as u8; o = o + 1 // 'T'
156 out[o] = 0x54 as u8; o = o + 1 // 'T'
157 out[o] = 0x50 as u8; o = o + 1 // 'P'
158 out[o] = 0x2f as u8; o = o + 1 // '/'
159 out[o] = 0x32 as u8; o = o + 1 // '2'
160 out[o] = 0x2e as u8; o = o + 1 // '.'
161 out[o] = 0x30 as u8; o = o + 1 // '0'
162 out[o] = 0x0d as u8; o = o + 1 // CR
163 out[o] = 0x0a as u8; o = o + 1 // LF
164 out[o] = 0x0d as u8; o = o + 1 // CR
165 out[o] = 0x0a as u8; o = o + 1 // LF
166 out[o] = 0x53 as u8; o = o + 1 // 'S'
167 out[o] = 0x4d as u8; o = o + 1 // 'M'
168 out[o] = 0x0d as u8; o = o + 1 // CR
169 out[o] = 0x0a as u8; o = o + 1 // LF
170 out[o] = 0x0d as u8; o = o + 1 // CR
171 out[o] = 0x0a as u8; o = o + 1 // LF
172 return o
173}
174
175// 1 = exact 24-byte preface match, 0 = mismatch, < 0 = fewer than 24 octets.
176func h2_check_preface(buf: *u8, off: i64, lim: i64) -> i64 {
177 if off + 24 > lim { return 0 - 1 }
178 let ref: *u8 = sys_mmap(32)
179 h2_write_preface(ref, 0)
180 var i: i64 = 0
181 while i < 24 {
182 if (buf[off + i] & 0xff) != (ref[i] & 0xff) { return 0 }
183 i = i + 1
184 }
185 return 1
186}
187
188// =====================================================================
189// §6.5 SETTINGS (type 0x04, stream MUST be 0). Payload = zero or more 6-octet
190// entries Identifier(16) + Value(32); length MUST be a multiple of 6.
191// =====================================================================
192func h2_frame_write_settings_empty(out: *u8, off: i64) -> i64 {
193 return h2_frame_write_header(out, off, 0, 0x04, 0x00, 0)
194}
195func h2_frame_write_settings_ack(out: *u8, off: i64) -> i64 {
196 // ACK flag 0x01; ACK MUST carry an empty payload.
197 return h2_frame_write_header(out, off, 0, 0x04, 0x01, 0)
198}
199// One SETTINGS frame carrying exactly one 6-octet parameter entry.
200func h2_frame_write_settings_param(out: *u8, off: i64, ident: i64, value: i64) -> i64 {
201 var o: i64 = h2_frame_write_header(out, off, 6, 0x04, 0x00, 0)
202 if o < 0 { return o }
203 tls_write_u16_be(out, o, ident & 0xffff); o = o + 2 // Identifier(16) -- composed from nx_tls13
204 h2_write_u32_be(out, o, value); o = o + 4 // Value(32)
205 return o
206}
207// A SETTINGS payload length is valid iff it is a multiple of 6 (else FRAME_SIZE_ERROR).
208func h2_settings_payload_valid(len: i64) -> i64 {
209 if len < 0 { return 0 }
210 if (len % 6) == 0 { return 1 }
211 return 0
212}
213
214// =====================================================================
215// §6.2 HEADERS (type 0x01). Caller supplies the HPACK block (built with
216// nx_hpack); the header Length is the block length. flags: END_STREAM=0x01,
217// END_HEADERS=0x04, PADDED=0x08, PRIORITY=0x20.
218// =====================================================================
219func h2_frame_write_headers(out: *u8, off: i64, stream_id: i64, flags: i64, block: *u8, blen: i64) -> i64 {
220 if blen < 0 { return 0 - 1 }
221 if stream_id == 0 { return 0 - 1 } // HEADERS MUST NOT be on stream 0
222 var o: i64 = h2_frame_write_header(out, off, blen, 0x01, flags, stream_id)
223 if o < 0 { return o }
224 var i: i64 = 0
225 while i < blen { out[o + i] = block[i]; i = i + 1 }
226 return o + blen
227}
228
229// =====================================================================
230// §6.1 DATA (type 0x00). flags: END_STREAM=0x01.
231// =====================================================================
232func h2_frame_write_data(out: *u8, off: i64, stream_id: i64, flags: i64, data: *u8, dlen: i64) -> i64 {
233 if dlen < 0 { return 0 - 1 }
234 if stream_id == 0 { return 0 - 1 } // DATA MUST NOT use stream 0
235 var o: i64 = h2_frame_write_header(out, off, dlen, 0x00, flags, stream_id)
236 if o < 0 { return o }
237 var i: i64 = 0
238 while i < dlen { out[o + i] = data[i]; i = i + 1 }
239 return o + dlen
240}
241
242// =====================================================================
243// §6.4 RST_STREAM (type 0x03). Payload = Error Code(32); length MUST be
244// exactly 4; stream MUST NOT be 0.
245// =====================================================================
246func h2_frame_write_rst_stream(out: *u8, off: i64, stream_id: i64, error_code: i64) -> i64 {
247 if stream_id == 0 { return 0 - 1 } // RST_STREAM MUST NOT be on stream 0
248 var o: i64 = h2_frame_write_header(out, off, 4, 0x03, 0x00, stream_id)
249 if o < 0 { return o }
250 h2_write_u32_be(out, o, error_code); o = o + 4
251 return o
252}
253// Refuse to emit an RST_STREAM whose declared payload length is not exactly 4.
254func h2_frame_write_rst_stream_checked(out: *u8, off: i64, stream_id: i64, error_code: i64, declared_len: i64) -> i64 {
255 if declared_len != 4 { return 0 - 1 } // FRAME_SIZE_ERROR class
256 return h2_frame_write_rst_stream(out, off, stream_id, error_code)
257}
258
259// =====================================================================
260// §6.7 PING (type 0x06). Payload = exactly 8 opaque octets; stream MUST be 0.
261// flags: ACK=0x01.
262// =====================================================================
263func h2_frame_write_ping(out: *u8, off: i64, flags: i64, opaque8: *u8) -> i64 {
264 var o: i64 = h2_frame_write_header(out, off, 8, 0x06, flags, 0)
265 if o < 0 { return o }
266 var i: i64 = 0
267 while i < 8 { out[o + i] = opaque8[i]; i = i + 1 }
268 return o + 8
269}
270
271// =====================================================================
272// §6.8 GOAWAY (type 0x07, stream MUST be 0).
273// R(1)=0 + Last-Stream-ID(31) | Error Code(32) | Debug Data(var)
274// =====================================================================
275func h2_frame_write_goaway(out: *u8, off: i64, last_stream_id: i64, error_code: i64, debug: *u8, dlen: i64) -> i64 {
276 if dlen < 0 { return 0 - 1 }
277 let length: i64 = 8 + dlen // 4 (last-stream-id) + 4 (error) + debug
278 var o: i64 = h2_frame_write_header(out, off, length, 0x07, 0x00, 0)
279 if o < 0 { return o }
280 h2_write_u32_be(out, o, last_stream_id & 0x7fffffff); o = o + 4
281 h2_write_u32_be(out, o, error_code); o = o + 4
282 var i: i64 = 0
283 while i < dlen { out[o + i] = debug[i]; i = i + 1 }
284 return o + dlen
285}
286
287// =====================================================================
288// §6.9 WINDOW_UPDATE (type 0x08). Payload = R(1)=0 + Window Size
289// Increment(31); length MUST be exactly 4.
290// =====================================================================
291func h2_frame_write_window_update(out: *u8, off: i64, stream_id: i64, increment: i64) -> i64 {
292 var o: i64 = h2_frame_write_header(out, off, 4, 0x08, 0x00, stream_id)
293 if o < 0 { return o }
294 h2_write_u32_be(out, o, increment & 0x7fffffff); o = o + 4
295 return o
296}
297
298// =====================================================================
299// GATE (main): RFC 9113 byte-exact KATs + header round-trips + tamper cases.
300// =====================================================================
301func h2_check_bytes(name: *u8, got: *u8, glen: i64, exp: *u8, elen: i64) -> i64 {
302 if glen != elen {
303 h2_puts(" FAIL " as *u8); h2_puts(name)
304 h2_puts(" length got=" as *u8); h2_putn(glen)
305 h2_puts(" exp=" as *u8); h2_putn(elen); h2_puts("\n" as *u8)
306 return 0
307 }
308 var i: i64 = 0
309 while i < glen {
310 if (got[i] & 0xff) != (exp[i] & 0xff) {
311 h2_puts(" FAIL " as *u8); h2_puts(name)
312 h2_puts(" byte[" as *u8); h2_putn(i)
313 h2_puts("] got=" as *u8); h2_puthex2(got[i] & 0xff)
314 h2_puts(" exp=" as *u8); h2_puthex2(exp[i] & 0xff); h2_puts("\n" as *u8)
315 return 0
316 }
317 i = i + 1
318 }
319 h2_puts(" PASS " as *u8); h2_puts(name)
320 h2_puts(" (" as *u8); h2_putn(glen); h2_puts(" bytes)\n" as *u8)
321 return 1
322}
323
324func main() -> i64 {
325 var pass: i64 = 0
326 var tot: i64 = 0
327 h2_puts("nx_h2_frame gate (RFC 9113 frame codec, FOUNDED on nx_hpack/nx_tls13)\n" as *u8)
328
329 let out: *u8 = sys_mmap(512)
330 let exp: *u8 = sys_mmap(512)
331
332 // ---- §3.4 connection preface (24 bytes) ----
333 var o: i64 = h2_write_preface(out, 0)
334 exp[0]=0x50 as u8; exp[1]=0x52 as u8; exp[2]=0x49 as u8; exp[3]=0x20 as u8
335 exp[4]=0x2a as u8; exp[5]=0x20 as u8; exp[6]=0x48 as u8; exp[7]=0x54 as u8
336 exp[8]=0x54 as u8; exp[9]=0x50 as u8; exp[10]=0x2f as u8; exp[11]=0x32 as u8
337 exp[12]=0x2e as u8; exp[13]=0x30 as u8; exp[14]=0x0d as u8; exp[15]=0x0a as u8
338 exp[16]=0x0d as u8; exp[17]=0x0a as u8; exp[18]=0x53 as u8; exp[19]=0x4d as u8
339 exp[20]=0x0d as u8; exp[21]=0x0a as u8; exp[22]=0x0d as u8; exp[23]=0x0a as u8
340 pass = pass + h2_check_bytes("preface (PRI * HTTP/2.0...)" as *u8, out, o, exp, 24); tot = tot + 1
341
342 // h2_check_preface on the 24 bytes -> 1
343 var pc: i64 = h2_check_preface(out, 0, 24)
344 if pc == 1 { h2_puts(" PASS h2_check_preface == 1\n" as *u8); pass = pass + 1 }
345 if pc != 1 { h2_puts(" FAIL h2_check_preface got=" as *u8); h2_putn(pc); h2_puts("\n" as *u8) }
346 tot = tot + 1
347
348 // ---- empty client SETTINGS (9 bytes): 00 00 00 04 00 00 00 00 00 ----
349 o = h2_frame_write_settings_empty(out, 0)
350 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x00 as u8; exp[3]=0x04 as u8
351 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
352 exp[8]=0x00 as u8
353 pass = pass + h2_check_bytes("empty client SETTINGS" as *u8, out, o, exp, 9); tot = tot + 1
354
355 // ---- SETTINGS ACK (9 bytes): 00 00 00 04 01 00 00 00 00 ----
356 o = h2_frame_write_settings_ack(out, 0)
357 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x00 as u8; exp[3]=0x04 as u8
358 exp[4]=0x01 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
359 exp[8]=0x00 as u8
360 pass = pass + h2_check_bytes("SETTINGS ACK" as *u8, out, o, exp, 9); tot = tot + 1
361
362 // ---- SETTINGS w/ INITIAL_WINDOW_SIZE(0x04)=65535 (15 bytes) ----
363 // 00 00 06 04 00 00 00 00 00 00 04 00 00 ff ff
364 o = h2_frame_write_settings_param(out, 0, 0x04, K_MAGIC_65535)
365 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x06 as u8; exp[3]=0x04 as u8
366 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
367 exp[8]=0x00 as u8; exp[9]=0x00 as u8; exp[10]=0x04 as u8; exp[11]=0x00 as u8
368 exp[12]=0x00 as u8; exp[13]=0xff as u8; exp[14]=0xff as u8
369 pass = pass + h2_check_bytes("SETTINGS INITIAL_WINDOW_SIZE=65535" as *u8, out, o, exp, 15); tot = tot + 1
370
371 // ---- HEADERS on stream 1, flags 0x05, wrapping the C.3.1 HPACK block ----
372 // Build the HPACK block exactly as nx_hpack C.3.1 (composes nx_hpack):
373 // :method GET (82), :scheme http (86), :path / (84), :authority www.example.com (41 0f ...)
374 let block: *u8 = sys_mmap(64)
375 let auth: *u8 = sys_mmap(32); nx_str_cpy(auth, "www.example.com" as *u8)
376 var bo: i64 = hpack_encode_indexed(block, 0, 2) // 82
377 bo = hpack_encode_indexed(block, bo, 6) // 86
378 bo = hpack_encode_indexed(block, bo, 4) // 84
379 bo = hpack_encode_lit_inc_indexed_name(block, bo, 1, auth, 15) // 41 0f www.example.com
380 // bo MUST be 20 (the C.3.1 block length)
381 o = h2_frame_write_headers(out, 0, 1, 0x05, block, bo)
382 // header skeleton: 00 00 14 01 05 00 00 00 01 then the 20 HPACK bytes
383 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x14 as u8; exp[3]=0x01 as u8
384 exp[4]=0x05 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
385 exp[8]=0x01 as u8
386 exp[9]=0x82 as u8; exp[10]=0x86 as u8; exp[11]=0x84 as u8
387 exp[12]=0x41 as u8; exp[13]=0x0f as u8
388 exp[14]=0x77 as u8; exp[15]=0x77 as u8; exp[16]=0x77 as u8; exp[17]=0x2e as u8
389 exp[18]=0x65 as u8; exp[19]=0x78 as u8; exp[20]=0x61 as u8; exp[21]=0x6d as u8
390 exp[22]=0x70 as u8; exp[23]=0x6c as u8; exp[24]=0x65 as u8; exp[25]=0x2e as u8
391 exp[26]=0x63 as u8; exp[27]=0x6f as u8; exp[28]=0x6d as u8
392 pass = pass + h2_check_bytes("HEADERS stream 1 flags 0x05 + C.3.1 block" as *u8, out, o, exp, 29); tot = tot + 1
393
394 // ---- WINDOW_UPDATE +65535 on connection (stream 0), 13 bytes ----
395 // 00 00 04 08 00 00 00 00 00 00 00 ff ff
396 o = h2_frame_write_window_update(out, 0, 0, K_MAGIC_65535)
397 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x04 as u8; exp[3]=0x08 as u8
398 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
399 exp[8]=0x00 as u8; exp[9]=0x00 as u8; exp[10]=0x00 as u8; exp[11]=0xff as u8
400 exp[12]=0xff as u8
401 pass = pass + h2_check_bytes("WINDOW_UPDATE +65535 conn" as *u8, out, o, exp, 13); tot = tot + 1
402
403 // ---- RST_STREAM stream 1, error CANCEL (0x08), 13 bytes ----
404 // 00 00 04 03 00 00 00 00 01 00 00 00 08
405 o = h2_frame_write_rst_stream(out, 0, 1, 0x08)
406 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x04 as u8; exp[3]=0x03 as u8
407 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
408 exp[8]=0x01 as u8; exp[9]=0x00 as u8; exp[10]=0x00 as u8; exp[11]=0x00 as u8
409 exp[12]=0x08 as u8
410 pass = pass + h2_check_bytes("RST_STREAM stream 1 CANCEL" as *u8, out, o, exp, 13); tot = tot + 1
411
412 // ---- PING (8 opaque octets 01..08), 17 bytes ----
413 // 00 00 08 06 00 00 00 00 00 01 02 03 04 05 06 07 08
414 let op8: *u8 = sys_mmap(16)
415 op8[0]=0x01 as u8; op8[1]=0x02 as u8; op8[2]=0x03 as u8; op8[3]=0x04 as u8
416 op8[4]=0x05 as u8; op8[5]=0x06 as u8; op8[6]=0x07 as u8; op8[7]=0x08 as u8
417 o = h2_frame_write_ping(out, 0, 0x00, op8)
418 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x08 as u8; exp[3]=0x06 as u8
419 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
420 exp[8]=0x00 as u8
421 exp[9]=0x01 as u8; exp[10]=0x02 as u8; exp[11]=0x03 as u8; exp[12]=0x04 as u8
422 exp[13]=0x05 as u8; exp[14]=0x06 as u8; exp[15]=0x07 as u8; exp[16]=0x08 as u8
423 pass = pass + h2_check_bytes("PING opaque 01..08" as *u8, out, o, exp, 17); tot = tot + 1
424
425 // ---- GOAWAY last-stream-id 1, error NO_ERROR (0x00), no debug, 17 bytes ----
426 // 00 00 08 07 00 00 00 00 00 00 00 00 01 00 00 00 00
427 let nodbg: *u8 = sys_mmap(8)
428 o = h2_frame_write_goaway(out, 0, 1, 0x00, nodbg, 0)
429 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x08 as u8; exp[3]=0x07 as u8
430 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
431 exp[8]=0x00 as u8
432 exp[9]=0x00 as u8; exp[10]=0x00 as u8; exp[11]=0x00 as u8; exp[12]=0x01 as u8
433 exp[13]=0x00 as u8; exp[14]=0x00 as u8; exp[15]=0x00 as u8; exp[16]=0x00 as u8
434 pass = pass + h2_check_bytes("GOAWAY last=1 NO_ERROR" as *u8, out, o, exp, 17); tot = tot + 1
435
436 // ---- DATA stream 1 flags END_STREAM (0x01) "OK" (2 bytes), 11 bytes ----
437 // 00 00 02 00 01 00 00 00 01 4f 4b
438 let body: *u8 = sys_mmap(8); body[0]=0x4f as u8; body[1]=0x4b as u8 // "OK"
439 o = h2_frame_write_data(out, 0, 1, 0x01, body, 2)
440 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x02 as u8; exp[3]=0x00 as u8
441 exp[4]=0x01 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
442 exp[8]=0x01 as u8; exp[9]=0x4f as u8; exp[10]=0x4b as u8
443 pass = pass + h2_check_bytes("DATA stream 1 END_STREAM OK" as *u8, out, o, exp, 11); tot = tot + 1
444
445 // ---- header round-trip: read back the HEADERS frame fields ----
446 // Re-emit the HEADERS frame, then h2_frame_read_header recovers (len,type,flags,sid).
447 o = h2_frame_write_headers(out, 0, 1, 0x05, block, bo) // o = 29
448 let rlen: *i64 = sys_mmap(16) as *i64
449 let rtype: *i64 = sys_mmap(16) as *i64
450 let rflags: *i64 = sys_mmap(16) as *i64
451 let rsid: *i64 = sys_mmap(16) as *i64
452 let rstart: i64 = h2_frame_read_header(out, 0, o, rlen, rtype, rflags, rsid)
453 var rt_ok: i64 = 1
454 if rstart != 9 { rt_ok = 0 }
455 if rlen[0] != 20 { rt_ok = 0 }
456 if rtype[0] != 0x01 { rt_ok = 0 }
457 if rflags[0] != 0x05 { rt_ok = 0 }
458 if rsid[0] != 1 { rt_ok = 0 }
459 if rt_ok == 1 {
460 h2_puts(" PASS read-header round-trip (len=20 type=1 flags=05 sid=1)\n" as *u8)
461 pass = pass + 1
462 }
463 if rt_ok == 0 {
464 h2_puts(" FAIL read-header round-trip len=" as *u8); h2_putn(rlen[0])
465 h2_puts(" type=" as *u8); h2_putn(rtype[0]); h2_puts(" flags=" as *u8); h2_putn(rflags[0])
466 h2_puts(" sid=" as *u8); h2_putn(rsid[0]); h2_puts(" start=" as *u8); h2_putn(rstart); h2_puts("\n" as *u8)
467 }
468 tot = tot + 1
469
470 // also read back the SETTINGS-param frame: len=6 type=4 flags=0 sid=0
471 o = h2_frame_write_settings_param(out, 0, 0x04, K_MAGIC_65535) // o = 15
472 let s2: i64 = h2_frame_read_header(out, 0, o, rlen, rtype, rflags, rsid)
473 var sp_ok: i64 = 1
474 if s2 != 9 { sp_ok = 0 }
475 if rlen[0] != 6 { sp_ok = 0 }
476 if rtype[0] != 0x04 { sp_ok = 0 }
477 if rsid[0] != 0 { sp_ok = 0 }
478 if h2_settings_payload_valid(rlen[0]) != 1 { sp_ok = 0 }
479 if sp_ok == 1 { h2_puts(" PASS read-header SETTINGS-param (len=6 multiple-of-6)\n" as *u8); pass = pass + 1 }
480 if sp_ok == 0 { h2_puts(" FAIL read-header SETTINGS-param\n" as *u8) }
481 tot = tot + 1
482
483 // ---- frame-type-code KAT (defect (a): every RFC 9113 §6 type byte) ----
484 // Build one frame of each kind into a SCRATCH buffer and read back the Type
485 // octet (header byte index 3) -- proves DATA=0x00 HEADERS=0x01 RST_STREAM=0x03
486 // SETTINGS=0x04 PING=0x06 GOAWAY=0x07 WINDOW_UPDATE=0x08 byte-exact. The
487 // prior code mis-set SETTINGS to 0x05 (PUSH_PROMISE); this asserts 0x04.
488 let tcbuf: *u8 = sys_mmap(64)
489 let op8b: *u8 = sys_mmap(16)
490 op8b[0]=0x00 as u8; op8b[1]=0x00 as u8; op8b[2]=0x00 as u8; op8b[3]=0x00 as u8
491 op8b[4]=0x00 as u8; op8b[5]=0x00 as u8; op8b[6]=0x00 as u8; op8b[7]=0x00 as u8
492 let dbody: *u8 = sys_mmap(8); dbody[0]=0x41 as u8
493 var tc_ok: i64 = 1
494 h2_frame_write_data(tcbuf, 0, 1, 0x00, dbody, 1)
495 if (tcbuf[3] & 0xff) != 0x00 { tc_ok = 0 } // DATA
496 h2_frame_write_headers(tcbuf, 0, 1, 0x04, dbody, 1)
497 if (tcbuf[3] & 0xff) != 0x01 { tc_ok = 0 } // HEADERS
498 h2_frame_write_rst_stream(tcbuf, 0, 1, 0x00)
499 if (tcbuf[3] & 0xff) != 0x03 { tc_ok = 0 } // RST_STREAM
500 h2_frame_write_settings_empty(tcbuf, 0)
501 if (tcbuf[3] & 0xff) != 0x04 { tc_ok = 0 } // SETTINGS (NOT 0x05)
502 h2_frame_write_ping(tcbuf, 0, 0x00, op8b)
503 if (tcbuf[3] & 0xff) != 0x06 { tc_ok = 0 } // PING
504 h2_frame_write_goaway(tcbuf, 0, 1, 0x00, dbody, 0)
505 if (tcbuf[3] & 0xff) != 0x07 { tc_ok = 0 } // GOAWAY
506 h2_frame_write_window_update(tcbuf, 0, 0, 1)
507 if (tcbuf[3] & 0xff) != 0x08 { tc_ok = 0 } // WINDOW_UPDATE
508 if tc_ok == 1 { h2_puts(" PASS frame-type codes (DATA=00 HEADERS=01 RST=03 SETTINGS=04 PING=06 GOAWAY=07 WU=08)\n" as *u8); pass = pass + 1 }
509 if tc_ok == 0 { h2_puts(" FAIL frame-type codes\n" as *u8) }
510 tot = tot + 1
511
512 // =================================================================
513 // TAMPER CASES -- each operates on a SEPARATE mmap'd buffer; the canonical
514 // KAT buffer `out` is NEVER mutated in place (defect (c) from the prior
515 // council: the KAT byte and the tamper mutation must not be entangled).
516 // =================================================================
517
518 // ---- TAMPER 1: corrupt the 'S' of "SM" in the preface -> check returns 0 ----
519 // Build a pristine preface into `out` (canonical, untouched), then copy it to
520 // a SEPARATE buffer and corrupt ONLY the copy. Assert the canonical still
521 // verifies (==1) AND the corrupted copy is rejected (==0).
522 o = h2_write_preface(out, 0) // canonical, NOT mutated
523 let tbuf: *u8 = sys_mmap(32) // separate tamper buffer
524 var ci: i64 = 0
525 while ci < 24 { tbuf[ci] = out[ci]; ci = ci + 1 }
526 tbuf[18] = 0x00 as u8 // 0x53 ('S') -> 0x00 on the COPY only
527 let canon_pc: i64 = h2_check_preface(out, 0, 24) // canonical must still be 1
528 let tpc: i64 = h2_check_preface(tbuf, 0, 24) // corrupted copy must be 0
529 var t1ok: i64 = 0
530 if canon_pc == 1 { if tpc == 0 { t1ok = 1 } }
531 if t1ok == 1 { h2_puts(" PASS tamper corrupt-preface rejected (canon=1 corrupt=0, separate buffer)\n" as *u8); pass = pass + 1 }
532 if t1ok == 0 { h2_puts(" FAIL tamper corrupt-preface (canon=" as *u8); h2_putn(canon_pc); h2_puts(" corrupt=" as *u8); h2_putn(tpc); h2_puts(")\n" as *u8) }
533 tot = tot + 1
534
535 // ---- TAMPER 2: SETTINGS length 5 (not a multiple of 6) -> rejected ----
536 var t2ok: i64 = 0
537 if h2_settings_payload_valid(5) == 0 { if h2_settings_payload_valid(6) == 1 { t2ok = 1 } }
538 if t2ok == 1 { h2_puts(" PASS tamper SETTINGS-len-5 rejected (len%6 check)\n" as *u8); pass = pass + 1 }
539 if t2ok == 0 { h2_puts(" FAIL tamper SETTINGS-len-5 NOT rejected\n" as *u8) }
540 tot = tot + 1
541
542 // ---- TAMPER 3: RST_STREAM declared with a non-4 payload -> refused (< 0) ----
543 let rst_rc: i64 = h2_frame_write_rst_stream_checked(out, 0, 1, 0x08, 5)
544 var t3ok: i64 = 0
545 if rst_rc < 0 { t3ok = 1 }
546 if t3ok == 1 { h2_puts(" PASS tamper RST_STREAM non-4 payload refused (rc=" as *u8); h2_putn(rst_rc); h2_puts(")\n" as *u8); pass = pass + 1 }
547 if t3ok == 0 { h2_puts(" FAIL tamper RST_STREAM non-4 payload NOT refused (rc=" as *u8); h2_putn(rst_rc); h2_puts(")\n" as *u8) }
548 tot = tot + 1
549
550 // ---- TAMPER 4: declared frame Length exceeds the buffer -> read rejects (< 0) ----
551 // Craft a 9-byte header claiming a 100-byte payload into a SEPARATE buffer,
552 // give lim=9 (no payload). Does not touch the canonical `out`.
553 let tbuf2: *u8 = sys_mmap(32)
554 h2_frame_write_header(tbuf2, 0, 100, 0x01, 0x00, 1)
555 let tlen: *i64 = sys_mmap(16) as *i64
556 let ttype: *i64 = sys_mmap(16) as *i64
557 let tflags: *i64 = sys_mmap(16) as *i64
558 let tsid: *i64 = sys_mmap(16) as *i64
559 let over_rc: i64 = h2_frame_read_header(tbuf2, 0, 9, tlen, ttype, tflags, tsid)
560 var t4ok: i64 = 0
561 if over_rc < 0 { t4ok = 1 }
562 if t4ok == 1 { h2_puts(" PASS tamper over-long frame-length rejected (rc=" as *u8); h2_putn(over_rc); h2_puts(")\n" as *u8); pass = pass + 1 }
563 if t4ok == 0 { h2_puts(" FAIL tamper over-long frame-length NOT rejected (rc=" as *u8); h2_putn(over_rc); h2_puts(")\n" as *u8) }
564 tot = tot + 1
565
566 h2_puts("---- h2_frame gate: passed " as *u8); h2_putn(pass); h2_puts(" / " as *u8); h2_putn(tot); h2_puts("\n" as *u8)
567 if pass == tot {
568 // DURABLE EVIDENCE (mirror nx_hpack.nx:544): stdout evaporates and cannot
569 // anchor a row_markers entry -- append ONE marker/reconcile-recognised
570 // status line to knowledge/status/. Only reached when EVERY KAT + the
571 // frame-type-code KAT + all four tamper cases pass (pass == tot).
572 let lfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_frame.log" as *u8, 0x1a4)
573 if lfd >= 0 {
574 sys_write(lfd, "R4-H2-003-GATE organ=nx_h2_frame kats=" as *u8, 38)
575 h2_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); h2_fdn(lfd, tot)
576 sys_write(lfd, " tamper=ok verdict=GREEN\n" as *u8, 25)
577 sys_close(lfd)
578 }
579 sys_exit(0)
580 }
581 sys_exit(1)
582 return 0
583}