code wiki / (root) / nx_h2_frame.nx

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}