code wiki / (root) / nx_h2_server.nx

nx_h2_server.nx source

↩ module page · 472 lines · 25688 B

1// nx_h2_server.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under the B1 2// rung(3) ALPN-h2 SERVER workflow of the R4-H2 HTTP/2-transport ladder), NOT 3// credited as team self-authoring. 4// 5// THE HTTP/2 SERVER REQUEST/RESPONSE framer -- the byte-exact SERVER TWIN of 6// nx_h2_client_over_tls.nx. Given the client's opening bytes (preface + client 7// SETTINGS + HEADERS wrapping an HPACK GET block), it: 8// recv: h2_check_preface -> walk frames -> on the first HEADERS, HPACK-decode 9// the request :method and :path (return them, or < 0 on malformed). 10// send: emit server SETTINGS + SETTINGS-ACK + HEADERS(:status 200 = HPACK 0x88, 11// END_HEADERS) + DATA(body, END_STREAM) on stream 1. 12// The h2 frame bytes ARE the allowed internet boundary (RFC 9113 / 7541 dictate 13// the octets); the implementation is pure NishiLang -- no nghttp2, no openssl. 14// 15// FOUNDED ON (composes, does NOT reinvent -- anti-orphan law; imported EXACTLY 16// ONCE, RC6 double-import avoided): 17// - nx_h2_conformance.nx (R4-H2-006, GREEN): pulls TRANSITIVELY (its single 18// import splices nx_h2_flow -> nx_h2_frame -> nx_hpack -> nx_str -> 19// nx_syscalls, plus nx_h2_stream and nx_tls13_ext) EVERYTHING we need: 20// h2_check_preface / h2_frame_read_header / the frame WRITERS 21// (h2_frame_write_settings_empty / _settings_ack / _headers / _data) / the 22// HPACK DECODERS (hpack_decode_int / hpack_decode_str) / the request-block 23// BUILDER (h2_build_client_open, h2_build_request_block) for the gate / 24// tls13_ext_emit_alpn_h2_only + tls13_ext_parse_alpn_selected for the ALPN 25// KAT / sys_mmap / sys_write / sys_exit / sys_openat_append / sys_close. 26// Importing nx_h2_frame / nx_hpack / nx_h2_flow / nx_h2_stream / nx_tls13_ext 27// directly TOO would be the RC6 double-import landmine. 28// - nx_h2_client_over_tls.nx (B1 rung(2), GREEN): the CLIENT extractor 29// h2_extract_status_and_body -- the gate feeds OUR server response THROUGH 30// it to prove the client recovers :status + body byte-exact (it imports 31// nx_h2_conformance ONCE, so this second import shares the same 32// include-guarded modules -- no double-import). We do NOT import the tls13 33// record/session modules here (those belong to the live loopback organ); the 34// in-memory gate needs only the framing. 35// 36// BACK-FILL: the team RE-AUTHORS this from the DATA spec 37// (knowledge/specs/2026-06-13-http2-transport-ladder.md) via the 38// emitter-of-emitters (X-AUT-006c/e/f); this hand scaffold is the sanctioned 39// one-time bootstrap only (meter-integrity, mirror nx_h2_conformance.nx:1-4). 40// 41// GATE (main): builds a client open with h2_build_client_open, feeds it through 42// h2_server_recv_request asserting :method GET + :path "/"; builds the response 43// with h2_server_send_response, feeds THAT through the client's 44// h2_extract_status_and_body asserting :status 200 + exact body; an ALPN KAT 45// asserting the server EE-ALPN inner == the byte-exact target AND 46// tls13_ext_parse_alpn_selected recovers "h2"; PLUS tampers (corrupted preface 47// rejected, DATA-on-stream-0 rejected, malformed request fabricates no :method). 48// 49// license_tier: INDEPENDENT_REDERIVE 50// genealogy_id: international-research-sources/ietf/rfc_9113 + rfc_7541 + rfc_8446 + rfc_7301 51// lineage_id: nishi_h2_server_b1r3 52 53import "nx_h2_conformance.nx" 54import "nx_h2_client_over_tls.nx" 55 56// ---- print helpers (renamed hs_*) ---- 57func hs_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 58func hs_putn(v: i64) -> i64 { 59 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 60 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 61 let t: *u8 = sys_mmap(28); var k: i64 = 0 62 while m > 0 { t[k] = (48 + (m - (m/10)*10)) as u8; m = m/10; k = k + 1 } 63 while k > 0 { k = k - 1; sys_write(1, (((t as i64)+k) as *u8), 1) } 64 return 0 65} 66func hs_fdn(fd: i64, v: i64) -> i64 { 67 if v == 0 { sys_write(fd, "0" as *u8, 1); return 0 } 68 var m: i64 = v; if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m } 69 let t: *u8 = sys_mmap(28); var k: i64 = 0 70 while m > 0 { t[k] = (48 + (m - (m/10)*10)) as u8; m = m/10; k = k + 1 } 71 while k > 0 { k = k - 1; sys_write(fd, (((t as i64)+k) as *u8), 1) } 72 return 0 73} 74 75// Method codes recovered by the request parser (small sealed enum). 76const H2_METHOD_UNKNOWN: i64 = 0 77const H2_METHOD_GET: i64 = 1 78const H2_METHOD_POST: i64 = 2 79 80// ===================================================================== 81// HPACK request-block walker. Decode the request pseudo-headers from a HEADERS 82// block [off,lim) and recover :method and :path -- the SERVER twin of the 83// client's h2_decode_status walker (nx_h2_client_over_tls.nx:110-178). 84// 85// The sovereign client sends EXACTLY '82 87 84 41 0b <authority>' 86// (h2_build_request_block, nx_h2_conformance.nx:114-138): 87// 82 = 6.1 Indexed static 2 -> :method GET 88// 87 = 6.1 Indexed static 7 -> :scheme https 89// 84 = 6.1 Indexed static 4 -> :path / 90// 41 0b <authority> = 6.2.1 literal w/ inc-indexing, name index 1 -> :authority 91// Non-"/" paths arrive as 6.2.2 literal w/o indexing, name index 4 (':path'), 92// carrying the raw path value (h2_build_request_block, plen != 1 branch). 93// 94// We map the relevant HPACK static indices (RFC 7541 Appendix A): 95// index 2 = ":method"="GET" index 3 = ":method"="POST" 96// index 4 = ":path"="/" index 5 = ":path"="/index.html" 97// index 17 = ":method" name (literal value follows, e.g. PUT) 98// index 4 = ":path" name (literal value follows for non-"/" paths) 99// 100// Outputs: *out_method (H2_METHOD_*), and the :path bytes (offset+len within 101// block). Returns 0 on a parse that recovered BOTH a method and a path, or a 102// negative code on malformed/unrecognized -- and on failure NEVER fabricates a 103// method (*out_method stays H2_METHOD_UNKNOWN). HONEST: an unknown 104// representation returns < 0, it does not silently default to GET. 105// ===================================================================== 106func h2_decode_request( 107 block: *u8, off: i64, lim: i64, 108 out_method: *i64, out_path_off: *i64, out_path_len: *i64 109) -> i64 { 110 var o: i64 = off 111 out_method[0] = H2_METHOD_UNKNOWN 112 out_path_off[0] = 0 - 1 113 out_path_len[0] = 0 - 1 114 var saw_method: i64 = 0 115 var saw_path: i64 = 0 116 let ibox: *i64 = sys_mmap(16) as *i64 117 let sptr: *i64 = sys_mmap(16) as *i64 118 let slen: *i64 = sys_mmap(16) as *i64 119 let shuf: *i64 = sys_mmap(16) as *i64 120 while o < lim { 121 let lead: i64 = block[o] & 0xff 122 if (lead & 0x80) == 0x80 { 123 // 6.1 Indexed (N=7). 124 let a: i64 = hpack_decode_int(block, o, lim, 7, ibox) 125 if a < 0 { return a } 126 let idx: i64 = ibox[0] 127 if idx == 2 { out_method[0] = H2_METHOD_GET; saw_method = 1 } // :method GET 128 if idx == 3 { out_method[0] = H2_METHOD_POST; saw_method = 1 } // :method POST 129 if idx == 4 { // :path / 130 out_path_off[0] = 0 - 1 // sentinel "/" (no value bytes) 131 out_path_len[0] = 1 // logical length of "/" 132 saw_path = 1 133 } 134 if idx == 5 { // :path /index.html (static) 135 saw_path = 1 136 out_path_off[0] = 0 - 2 // sentinel for /index.html 137 out_path_len[0] = 11 138 } 139 o = a 140 } else { if (lead & 0xc0) == 0x40 { 141 // 6.2.1 literal w/ incremental indexing, N=6 name index. 142 let a2: i64 = hpack_decode_int(block, o, lim, 6, ibox) 143 if a2 < 0 { return a2 } 144 let nidx: i64 = ibox[0] 145 let aval: i64 = hpack_decode_str(block, a2, lim, sptr, slen, shuf) 146 if aval < 0 { return aval } 147 // :authority (name index 1) and other literal-inc fields -> walk past. 148 o = aval 149 } else { if (lead & 0xf0) == 0x00 { 150 // 6.2.2 literal w/o indexing, N=4 name index (this is how a non-"/" 151 // :path arrives, name index 4). 152 let a3: i64 = hpack_decode_int(block, o, lim, 4, ibox) 153 if a3 < 0 { return a3 } 154 let nidx3: i64 = ibox[0] 155 let aval3: i64 = hpack_decode_str(block, a3, lim, sptr, slen, shuf) 156 if aval3 < 0 { return aval3 } 157 if nidx3 == 4 { // :path literal value 158 if shuf[0] == 0 { // raw (not Huffman) 159 out_path_off[0] = sptr[0] 160 out_path_len[0] = slen[0] 161 saw_path = 1 162 } 163 } 164 if nidx3 == 17 { // :method literal value 165 if shuf[0] == 0 { 166 // value bytes at sptr[0], len slen[0]; map GET/POST. 167 if slen[0] == 3 { 168 if (block[sptr[0]] & 0xff) == 71 { // 'G' 169 out_method[0] = H2_METHOD_GET; saw_method = 1 170 } 171 } 172 if slen[0] == 4 { 173 if (block[sptr[0]] & 0xff) == 80 { // 'P' (POST) 174 out_method[0] = H2_METHOD_POST; saw_method = 1 175 } 176 } 177 } 178 } 179 o = aval3 180 } else { if (lead & 0xf0) == 0x10 { 181 // 6.2.3 literal never indexed, N=4 name index. 182 let a4: i64 = hpack_decode_int(block, o, lim, 4, ibox) 183 if a4 < 0 { return a4 } 184 let aval4: i64 = hpack_decode_str(block, a4, lim, sptr, slen, shuf) 185 if aval4 < 0 { return aval4 } 186 o = aval4 187 } else { if (lead & 0xe0) == 0x20 { 188 // 5.3 dynamic table size update (001x xxxx, N=5) -- no header, skip. 189 let a5: i64 = hpack_decode_int(block, o, lim, 5, ibox) 190 if a5 < 0 { return a5 } 191 o = a5 192 } else { 193 return 0 - 9 // unrecognized representation 194 } } } } } 195 } 196 if saw_method == 0 { return 0 - 8 } // no :method -> honest fail 197 if saw_path == 0 { return 0 - 7 } // no :path -> honest fail 198 return 0 199} 200 201// ===================================================================== 202// h2_server_recv_request -- consume the client's opening bytes [0,n): the 203// 24-byte connection preface (h2_check_preface), then walk frames; on the FIRST 204// HEADERS frame, HPACK-decode the request block to recover :method and :path. 205// 206// Returns 0 on success (out_method/out_path_* populated), or a NEGATIVE code: 207// -100 invalid/corrupted preface (h2_check_preface != 1) 208// -101 a DATA frame appeared on stream 0 (illegal, PROTOCOL_ERROR class) 209// -102 a DATA/HEADERS frame appeared on the request stream BEFORE any HEADERS 210// (a request must open with HEADERS) -- never fabricate a method 211// -103 no HEADERS frame seen at all 212// <0 propagated h2_frame_read_header / h2_decode_request failure 213// On ANY failure path out_method stays H2_METHOD_UNKNOWN (no fabrication). 214// ===================================================================== 215func h2_server_recv_request( 216 buf: *u8, n: i64, 217 out_method: *i64, out_path_off: *i64, out_path_len: *i64, 218 out_path_buf: *u8 219) -> i64 { 220 out_method[0] = H2_METHOD_UNKNOWN 221 out_path_off[0] = 0 - 1 222 out_path_len[0] = 0 - 1 223 // ---- preface MUST be the exact 24 bytes ---- 224 if n < 24 { return 0 - 100 } 225 if h2_check_preface(buf, 0, 24) != 1 { return 0 - 100 } 226 227 var o: i64 = 24 228 var saw_headers: i64 = 0 229 let rlen: *i64 = sys_mmap(16) as *i64 230 let rtype: *i64 = sys_mmap(16) as *i64 231 let rflags: *i64 = sys_mmap(16) as *i64 232 let rsid: *i64 = sys_mmap(16) as *i64 233 while o < n { 234 let pstart: i64 = h2_frame_read_header(buf, o, n, rlen, rtype, rflags, rsid) 235 if pstart < 0 { return pstart } // truncated / over-long -> propagate honest <0 236 let ftype: i64 = rtype[0] 237 let plen: i64 = rlen[0] 238 let sid: i64 = rsid[0] 239 if ftype == 0x00 { // DATA 240 if sid == 0 { return 0 - 101 } // DATA MUST NOT be on stream 0 241 if saw_headers == 0 { return 0 - 102 } // DATA before HEADERS -> reject, no fabricated method 242 } 243 if ftype == 0x01 { // HEADERS 244 if sid == 0 { return 0 - 101 } // HEADERS MUST NOT be on stream 0 245 if saw_headers == 0 { 246 let dr: i64 = h2_decode_request(buf, pstart, pstart + plen, out_method, out_path_off, out_path_len) 247 if dr < 0 { 248 out_method[0] = H2_METHOD_UNKNOWN // ensure no fabrication on malformed 249 return dr 250 } 251 // Materialize the :path bytes into out_path_buf for the caller. 252 // Sentinel -1 means the indexed "/" path; -2 means /index.html. 253 if out_path_off[0] == (0 - 1) { 254 out_path_buf[0] = 0x2f as u8 // '/' 255 out_path_len[0] = 1 256 } 257 if out_path_off[0] == (0 - 2) { 258 let idx: *u8 = "/index.html" as *u8 259 var ci: i64 = 0 260 while ci < 11 { out_path_buf[ci] = idx[ci]; ci = ci + 1 } 261 out_path_len[0] = 11 262 } 263 if out_path_off[0] >= 0 { 264 var cj: i64 = 0 265 while cj < out_path_len[0] { 266 out_path_buf[cj] = buf[out_path_off[0] + cj] 267 cj = cj + 1 268 } 269 } 270 saw_headers = 1 271 } 272 } 273 o = pstart + plen 274 } 275 if saw_headers == 0 { return 0 - 103 } // no request HEADERS at all 276 return 0 277} 278 279// ===================================================================== 280// h2_server_send_response -- emit the server's h2 response into `out` at off 0: 281// server SETTINGS (empty) + SETTINGS-ACK + HEADERS(:status 200, END_HEADERS) 282// + DATA(body, END_STREAM) on stream 1. 283// status MUST be 200 in this rung (HPACK indexed 0x88). Returns the total byte 284// count written, or < 0 on a builder error. The byte-exact SERVER twin of the 285// client's hc_synth_response (nx_h2_client_over_tls.nx:517-529). 286// ===================================================================== 287func h2_server_send_response(out: *u8, status: i64, body: *u8, blen: i64) -> i64 { 288 if status != 200 { return 0 - 1 } // this rung serves only 200 (HPACK 0x88) 289 if blen < 0 { return 0 - 1 } 290 var ro: i64 = 0 291 ro = h2_frame_write_settings_empty(out, ro) // peer SETTINGS 292 if ro < 0 { return ro } 293 ro = h2_frame_write_settings_ack(out, ro) // SETTINGS-ACK 294 if ro < 0 { return ro } 295 let hblk: *u8 = sys_mmap(16); hblk[0] = 0x88 as u8 // indexed :status 200 296 ro = h2_frame_write_headers(out, ro, 1, 0x04, hblk, 1) // END_HEADERS (not END_STREAM, body follows) 297 if ro < 0 { return ro } 298 ro = h2_frame_write_data(out, ro, 1, 0x01, body, blen) // DATA END_STREAM 299 if ro < 0 { return ro } 300 return ro 301} 302 303// ===================================================================== 304// GATE (main): in-memory round-trip vs the client open builder + the client 305// extractor, ALPN-EE byte KAT, and tampers. No sockets (the live loopback proof 306// is the authored loopback gate organ + the next phase's serve run). 307// ===================================================================== 308func main() -> i64 { 309 var pass: i64 = 0 310 var tot: i64 = 0 311 hs_puts("nx_h2_server gate (RFC 9113/7541/8446/7301, FOUNDED on h2_conformance+h2_client_over_tls)\n" as *u8) 312 313 // ---- KAT 1: round-trip -- client open -> server recv -> :method GET, :path "/" ---- 314 let authority: *u8 = sys_mmap(32); nx_str_cpy(authority, "localhost" as *u8) 315 let path: *u8 = sys_mmap(8); nx_str_cpy(path, "/" as *u8) 316 let open: *u8 = sys_mmap(512) 317 let openn: i64 = h2_build_client_open(open, 0, authority, 9, path, 1) 318 let m1: *i64 = sys_mmap(16) as *i64 319 let po1: *i64 = sys_mmap(16) as *i64 320 let pl1: *i64 = sys_mmap(16) as *i64 321 let pbuf1: *u8 = sys_mmap(64) 322 let rc1: i64 = h2_server_recv_request(open, openn, m1, po1, pl1, pbuf1) 323 var k1: i64 = 0 324 if rc1 == 0 { if m1[0] == H2_METHOD_GET { if pl1[0] == 1 { if (pbuf1[0] & 0xff) == 0x2f { k1 = 1 } } } } 325 if k1 == 1 { hs_puts(" PASS recv client-open -> :method GET, :path '/' (1 byte)\n" as *u8); pass = pass + 1 } 326 if k1 == 0 { hs_puts(" FAIL recv: rc=" as *u8); hs_putn(rc1); hs_puts(" method=" as *u8); hs_putn(m1[0]); hs_puts(" path_len=" as *u8); hs_putn(pl1[0]); hs_puts("\n" as *u8) } 327 tot = tot + 1 328 329 // ---- KAT 2: server response -> client extractor recovers :status 200 + exact body ---- 330 let body2: *u8 = sys_mmap(64) 331 let bodytext: *u8 = "hello from nishi h2 server" as *u8 332 var blen2: i64 = 0 333 while bodytext[blen2] != (0 as u8) { body2[blen2] = bodytext[blen2]; blen2 = blen2 + 1 } // 26 bytes 334 let resp2: *u8 = sys_mmap(256) 335 let r2: i64 = h2_server_send_response(resp2, 200, body2, blen2) 336 let ob2: *u8 = sys_mmap(256) 337 let st2: *i64 = sys_mmap(16) as *i64 338 let bl2: i64 = h2_extract_status_and_body(resp2, 0, r2, ob2, 256, st2) 339 var k2: i64 = 0 340 if st2[0] == 200 { if bl2 == blen2 { 341 var ok: i64 = 1; var m: i64 = 0 342 while m < blen2 { if (ob2[m] & 0xff) != (body2[m] & 0xff) { ok = 0 } m = m + 1 } 343 if ok == 1 { k2 = 1 } 344 } } 345 if k2 == 1 { hs_puts(" PASS server response -> client recovered :status 200 + body (" as *u8); hs_putn(blen2); hs_puts(" bytes byte-exact)\n" as *u8); pass = pass + 1 } 346 if k2 == 0 { hs_puts(" FAIL response round-trip: status=" as *u8); hs_putn(st2[0]); hs_puts(" body_len=" as *u8); hs_putn(bl2); hs_puts(" exp=" as *u8); hs_putn(blen2); hs_puts("\n" as *u8) } 347 tot = tot + 1 348 349 // ---- KAT 3: ALPN-EE byte KAT. Build the 15-byte ALPN-h2 EE inner exactly as 350 // nx_tls13_server_session_emit_ee_alpn builds it (the 0..6 header + the 351 // GREEN tls13_ext_emit_alpn_h2_only tail), assert it equals the byte-exact 352 // target '08 00 00 0b 00 09 00 10 00 05 00 03 02 68 32', AND the client 353 // parser tls13_ext_parse_alpn_selected on its ext_data recovers "h2". ---- 354 let ee: *u8 = sys_mmap(32) 355 ee[0]=0x08 as u8; ee[1]=0x00 as u8; ee[2]=0x00 as u8; ee[3]=0x0b as u8 356 ee[4]=0x00 as u8; ee[5]=0x09 as u8 357 let an: i64 = tls13_ext_emit_alpn_h2_only((ee as i64 + 6) as *u8, 26) 358 let target: *u8 = sys_mmap(32) 359 target[0]=0x08 as u8; target[1]=0x00 as u8; target[2]=0x00 as u8; target[3]=0x0b as u8 360 target[4]=0x00 as u8; target[5]=0x09 as u8; target[6]=0x00 as u8; target[7]=0x10 as u8 361 target[8]=0x00 as u8; target[9]=0x05 as u8; target[10]=0x00 as u8; target[11]=0x03 as u8 362 target[12]=0x02 as u8; target[13]=0x68 as u8; target[14]=0x32 as u8 363 var bytes_ok: i64 = 1 364 if an != 9 { bytes_ok = 0 } 365 var bi: i64 = 0 366 while bi < 15 { if (ee[bi] & 0xff) != (target[bi] & 0xff) { bytes_ok = 0 } bi = bi + 1 } 367 // Now parse the ALPN ext_data (PAST ext_type 0x0010 + ext_data_len 0x0005), 368 // i.e. ee[10..15) = 00 03 02 68 32 (len 5), recover "h2". 369 let sp: *i64 = sys_mmap(16) as *i64 370 let sl: *i64 = sys_mmap(16) as *i64 371 let prc: i64 = tls13_ext_parse_alpn_selected((ee as i64 + 10) as *u8, 5, sp, sl) 372 var parse_ok: i64 = 0 373 if prc == 1 { if sl[0] == 2 { 374 let nb: *u8 = (ee as i64 + 10 + sp[0]) as *u8 375 if (nb[0] & 0xff) == 0x68 { if (nb[1] & 0xff) == 0x32 { parse_ok = 1 } } 376 } } 377 var k3: i64 = 0 378 if bytes_ok == 1 { if parse_ok == 1 { k3 = 1 } } 379 if k3 == 1 { hs_puts(" PASS ALPN-EE inner byte-exact (15 bytes) + client parser recovers 'h2'\n" as *u8); pass = pass + 1 } 380 if k3 == 0 { hs_puts(" FAIL ALPN-EE: bytes_ok=" as *u8); hs_putn(bytes_ok); hs_puts(" parse_ok=" as *u8); hs_putn(parse_ok); hs_puts(" prc=" as *u8); hs_putn(prc); hs_puts("\n" as *u8) } 381 tot = tot + 1 382 383 // ---- TAMPER 1: corrupt the preface 'S' (byte 18) on a SEPARATE copy -> reject ---- 384 let tbuf: *u8 = sys_mmap(512) 385 var ci: i64 = 0 386 while ci < openn { tbuf[ci] = open[ci]; ci = ci + 1 } 387 tbuf[18] = 0x00 as u8 // corrupt 'S' of "SM" 388 let mt: *i64 = sys_mmap(16) as *i64 389 let pot: *i64 = sys_mmap(16) as *i64 390 let plt: *i64 = sys_mmap(16) as *i64 391 let pbt: *u8 = sys_mmap(64) 392 let rct: i64 = h2_server_recv_request(tbuf, openn, mt, pot, plt, pbt) 393 var k4: i64 = 0 394 if rct < 0 { if mt[0] == H2_METHOD_UNKNOWN { k4 = 1 } } // rejected AND no fabricated method 395 if k4 == 1 { hs_puts(" PASS tamper corrupt-preface rejected (rc=" as *u8); hs_putn(rct); hs_puts(", method NOT fabricated)\n" as *u8); pass = pass + 1 } 396 if k4 == 0 { hs_puts(" FAIL tamper corrupt-preface: rc=" as *u8); hs_putn(rct); hs_puts(" method=" as *u8); hs_putn(mt[0]); hs_puts("\n" as *u8) } 397 tot = tot + 1 398 399 // ---- TAMPER 2: a valid preface then a DATA frame on stream 0 (illegal) -> 400 // reject (-101), never fabricate a method. ---- 401 let tbuf2: *u8 = sys_mmap(64) 402 var to: i64 = h2_write_preface(tbuf2, 0) // 24-byte preface 403 // craft an illegal DATA frame on stream 0 via the raw header writer (the safe 404 // h2_frame_write_data refuses stream 0, so we craft the bytes ourselves). 405 to = h2_frame_write_header(tbuf2, to, 2, 0x00, 0x01, 0) // len=2 type=DATA flags=END_STREAM sid=0 406 tbuf2[to] = 0x4f as u8; tbuf2[to + 1] = 0x4b as u8; to = to + 2 // "OK" payload 407 let mt2: *i64 = sys_mmap(16) as *i64 408 let pot2: *i64 = sys_mmap(16) as *i64 409 let plt2: *i64 = sys_mmap(16) as *i64 410 let pbt2: *u8 = sys_mmap(64) 411 let rct2: i64 = h2_server_recv_request(tbuf2, to, mt2, pot2, plt2, pbt2) 412 var k5: i64 = 0 413 if rct2 < 0 { if mt2[0] == H2_METHOD_UNKNOWN { k5 = 1 } } 414 if k5 == 1 { hs_puts(" PASS tamper DATA-on-stream-0 rejected (rc=" as *u8); hs_putn(rct2); hs_puts(", method NOT fabricated)\n" as *u8); pass = pass + 1 } 415 if k5 == 0 { hs_puts(" FAIL tamper DATA-on-stream-0: rc=" as *u8); hs_putn(rct2); hs_puts(" method=" as *u8); hs_putn(mt2[0]); hs_puts("\n" as *u8) } 416 tot = tot + 1 417 418 // ---- TAMPER 3: a valid preface + SETTINGS but NO HEADERS frame -> reject 419 // (-103), never fabricate a method. ---- 420 let tbuf3: *u8 = sys_mmap(64) 421 var t3o: i64 = h2_write_preface(tbuf3, 0) 422 t3o = h2_frame_write_settings_empty(tbuf3, t3o) // SETTINGS but no HEADERS 423 let mt3: *i64 = sys_mmap(16) as *i64 424 let pot3: *i64 = sys_mmap(16) as *i64 425 let plt3: *i64 = sys_mmap(16) as *i64 426 let pbt3: *u8 = sys_mmap(64) 427 let rct3: i64 = h2_server_recv_request(tbuf3, t3o, mt3, pot3, plt3, pbt3) 428 var k6: i64 = 0 429 if rct3 == (0 - 103) { if mt3[0] == H2_METHOD_UNKNOWN { k6 = 1 } } 430 if k6 == 1 { hs_puts(" PASS tamper no-HEADERS rejected (rc=-103, method NOT fabricated)\n" as *u8); pass = pass + 1 } 431 if k6 == 0 { hs_puts(" FAIL tamper no-HEADERS: rc=" as *u8); hs_putn(rct3); hs_puts(" method=" as *u8); hs_putn(mt3[0]); hs_puts("\n" as *u8) } 432 tot = tot + 1 433 434 // ---- KAT 4 (round-trip with non-"/" path): client open GET /api -> server 435 // recovers :path "/api" via the literal-value branch. ---- 436 let path4: *u8 = sys_mmap(8); nx_str_cpy(path4, "/api" as *u8) 437 let open4: *u8 = sys_mmap(512) 438 let on4: i64 = h2_build_client_open(open4, 0, authority, 9, path4, 4) 439 let m4: *i64 = sys_mmap(16) as *i64 440 let po4: *i64 = sys_mmap(16) as *i64 441 let pl4: *i64 = sys_mmap(16) as *i64 442 let pb4: *u8 = sys_mmap(64) 443 let rc4: i64 = h2_server_recv_request(open4, on4, m4, po4, pl4, pb4) 444 var k7: i64 = 0 445 if rc4 == 0 { if m4[0] == H2_METHOD_GET { if pl4[0] == 4 { 446 if (pb4[0] & 0xff)==0x2f { if (pb4[1] & 0xff)==0x61 { if (pb4[2] & 0xff)==0x70 { if (pb4[3] & 0xff)==0x69 { k7 = 1 } } } } 447 } } } 448 if k7 == 1 { hs_puts(" PASS recv client-open GET /api -> :method GET, :path '/api' (4 bytes)\n" as *u8); pass = pass + 1 } 449 if k7 == 0 { hs_puts(" FAIL recv /api: rc=" as *u8); hs_putn(rc4); hs_puts(" method=" as *u8); hs_putn(m4[0]); hs_puts(" path_len=" as *u8); hs_putn(pl4[0]); hs_puts("\n" as *u8) } 450 tot = tot + 1 451 452 hs_puts("---- h2_server gate: passed " as *u8); hs_putn(pass); hs_puts(" / " as *u8); hs_putn(tot); hs_puts("\n" as *u8) 453 if pass == tot { 454 let lfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_server.log" as *u8, 0x1a4) 455 if lfd >= 0 { 456 sys_write(lfd, "B1-R3-H2-SERVE-GATE organ=nx_h2_server kats=" as *u8, 44) 457 hs_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); hs_fdn(lfd, tot) 458 sys_write(lfd, " alpn_ee=byte-exact tamper=ok verdict=GREEN\n" as *u8, 44) 459 sys_close(lfd) 460 } 461 sys_exit(0) 462 } 463 let rfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_server.log" as *u8, 0x1a4) 464 if rfd >= 0 { 465 sys_write(rfd, "B1-R3-H2-SERVE-GATE organ=nx_h2_server kats=" as *u8, 44) 466 hs_fdn(rfd, pass); sys_write(rfd, "/" as *u8, 1); hs_fdn(rfd, tot) 467 sys_write(rfd, " verdict=RED\n" as *u8, 12) 468 sys_close(rfd) 469 } 470 sys_exit(1) 471 return 0 472}