code wiki / (root) / nx_h2_client_over_tls.nx

nx_h2_client_over_tls.nx source

↩ module page · 834 lines · 44335 B

1// nx_h2_client_over_tls.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under 2// the R4-H2 HTTP/2-transport-ladder workflow, capstone NBC-FETCH-001), NOT 3// credited as team self-authoring. 4// 5// THE HTTP/2 CLIENT REQUEST/RESPONSE DRIVER carried over the sovereign TLS 1.3 6// application-data channel. Given a CONNECTED *Tls13ClientSession whose ALPN 7// negotiated "h2" (via nx_tls13_client_session_run_h2), it performs ONE HTTP/2 8// GET: 9// send (one TLS app-data record): preface + empty SETTINGS + HEADERS(stream 1, 10// END_STREAM|END_HEADERS) wrapping an HPACK GET block (= h2_build_client_open) 11// + a SETTINGS-ACK once the peer SETTINGS arrives. 12// recv (loop TLS records -> decrypt -> append to an h2 frame buffer): 13// walk frames, HPACK-decode the response HEADERS block's :status, and 14// reassemble DATA payloads into the body until END_STREAM. 15// The h2 frame bytes ARE the allowed internet-boundary (RFC 9113/7541 dictate the 16// octets); the implementation is pure NishiLang -- no nghttp2, no openssl. 17// 18// FOUNDED ON (composes, does NOT reinvent -- anti-orphan law; imported EXACTLY 19// ONCE each, RC6 double-import avoided): 20// - nx_h2_conformance.nx (R4-H2-006, GREEN): h2_build_client_open + the frame 21// builders/reader + HPACK encoders/decoders + flow windows, all pulled 22// transitively through its single import (it splices h2_flow -> h2_frame -> 23// hpack -> str -> syscalls, plus h2_stream and tls13_ext). 24// - nx_tls13_record.nx: AEAD app-data encrypt/decrypt (nx_tls13_record_*_v2). 25// - nx_tls13_read_record_from_fd.nx: one inbound TLS record (partial-read framer). 26// - nx_tls13_client_session.nx: the *Tls13ClientSession struct (app keys/iv/seq). 27// nx_tls13_record / read_record / client_session all transitively import 28// nx_tls13.nx and nx_syscalls.nx -- included once by the linker guards. 29// 30// BACK-FILL: the team RE-AUTHORS this from the DATA spec 31// (knowledge/specs/2026-06-13-http2-transport-ladder.md) via the 32// emitter-of-emitters (X-AUT-006c/e/f); this hand scaffold is the sanctioned 33// one-time bootstrap only (meter-integrity, mirror nx_h2_conformance.nx:1-4). 34// 35// GATE (main): synthesizes a server h2 response with OUR frame builders (peer 36// SETTINGS + SETTINGS-ACK + HEADERS :status 200 = HPACK 0x88 + DATA "hello h2" 37// END_STREAM), feeds it through h2_extract_status_and_body, and asserts the 38// recovered :status == 200 and the exact reassembled body, byte-for-byte. PLUS a 39// TAMPER case on a SEPARATE buffer: a HEADERS block carrying :status 404 must 40// recover 404 (not silently coerced to 200), and a body-less END_STREAM HEADERS 41// must yield body_len 0 -- never a fabricated body. 42// 43// license_tier: INDEPENDENT_REDERIVE 44// genealogy_id: international-research-sources/ietf/rfc_9113 + rfc_7541 + rfc_8446 45// lineage_id: nishi_h2_client_over_tls_r4h2_capstone 46 47import "nx_h2_conformance.nx" 48import "nx_tls13_record.nx" 49import "nx_tls13_read_record_from_fd.nx" 50import "nx_tls13_client_session.nx" 51const NX_MAGIC_16645: i64 = 16645 52const NX_MAGIC_2048: i64 = 2048 53const NX_MAGIC_300000: i64 = 300000 54const NX_MAGIC_8192: i64 = 8192 55const NX_MAGIC_16384: i64 = 16384 56const NX_MAGIC_4096: i64 = 4096 57 58// ---- print helpers (renamed hc_*) ---- 59func hc_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 60func hc_putn(v: i64) -> i64 { 61 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 62 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 63 let t: *u8 = sys_mmap(28); var k: i64 = 0 64 while m > 0 { t[k] = (48 + (m - (m/10)*10)) as u8; m = m/10; k = k + 1 } 65 while k > 0 { k = k - 1; sys_write(1, (((t as i64)+k) as *u8), 1) } 66 return 0 67} 68func hc_fdn(fd: i64, v: i64) -> i64 { 69 if v == 0 { sys_write(fd, "0" as *u8, 1); return 0 } 70 var m: i64 = v; if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m } 71 let t: *u8 = sys_mmap(28); var k: i64 = 0 72 while m > 0 { t[k] = (48 + (m - (m/10)*10)) as u8; m = m/10; k = k + 1 } 73 while k > 0 { k = k - 1; sys_write(fd, (((t as i64)+k) as *u8), 1) } 74 return 0 75} 76 77// ===================================================================== 78// HPACK :status decoder. Walks a response HEADERS block field-by-field and 79// returns the numeric :status, or < 0 if not found / malformed. 80// 81// :status is HPACK static index 8..14 (Appendix A): 8=200, 9=204, 10=206, 82// 11=304, 12=400, 13=404, 14=500. Representations a real server uses: 83// - 6.1 Indexed (0x88..0x8e) -> the value is one of the 7 above. 84// - 6.2.1/6.2.2 literal w/ indexed NAME 8 -> name=":status", value = digits. 85// - 6.2.x literal w/ NEW name -> name string equals ":status". 86// We parse ASCII status digits from the value string for the literal forms and 87// map the indexed forms directly. Dynamic-table references (index >= 62) for 88// :status do not occur from a server's first response and are skipped. 89// ===================================================================== 90func h2_status_from_static_index(idx: i64) -> i64 { 91 if idx == 8 { return 200 } 92 if idx == 9 { return 204 } 93 if idx == 10 { return 206 } 94 if idx == 11 { return 304 } 95 if idx == 12 { return 400 } 96 if idx == 13 { return 404 } 97 if idx == 14 { return 500 } 98 return 0 - 1 99} 100// parse up to 3 ASCII digits at buf[off..off+len) -> integer, or <0 if non-digit. 101func h2_atoi_n(buf: *u8, off: i64, len: i64) -> i64 { 102 if len < 1 { return 0 - 1 } 103 if len > 3 { return 0 - 1 } 104 var v: i64 = 0 105 var i: i64 = 0 106 while i < len { 107 let c: i64 = buf[off + i] & 0xff 108 if c < 48 { return 0 - 1 } 109 if c > 57 { return 0 - 1 } 110 v = v * 10 + (c - 48) 111 i = i + 1 112 } 113 return v 114} 115// Decode the whole header block [off,lim); return :status (>0) or <0. 116func h2_decode_status(block: *u8, off: i64, lim: i64) -> i64 { 117 var o: i64 = off 118 let ibox: *i64 = sys_mmap(16) as *i64 119 let sptr: *i64 = sys_mmap(16) as *i64 120 let slen: *i64 = sys_mmap(16) as *i64 121 let shuf: *i64 = sys_mmap(16) as *i64 122 while o < lim { 123 let lead: i64 = block[o] & 0xff 124 if (lead & 0x80) == 0x80 { 125 // 6.1 Indexed (N=7). value index in the low 7 bits. 126 let a: i64 = hpack_decode_int(block, o, lim, 7, ibox) 127 if a < 0 { return a } 128 let idx: i64 = ibox[0] 129 let st: i64 = h2_status_from_static_index(idx) 130 if st > 0 { return st } // an indexed :status -> done 131 o = a // other indexed header (e.g. :path) -> skip 132 } else { if (lead & 0xc0) == 0x40 { 133 // 6.2.1 literal w/ incremental indexing, N=6 name index. 134 let a2: i64 = hpack_decode_int(block, o, lim, 6, ibox) 135 if a2 < 0 { return a2 } 136 let nidx: i64 = ibox[0] 137 let aval: i64 = hpack_decode_str(block, a2, lim, sptr, slen, shuf) 138 if aval < 0 { return aval } 139 if nidx >= 8 { if nidx <= 14 { // name index in :status band 140 if shuf[0] == 0 { // raw ASCII digits (not Huffman) 141 let st2: i64 = h2_atoi_n(block, sptr[0], slen[0]) 142 if st2 > 0 { return st2 } 143 } 144 } } 145 o = aval 146 } else { if (lead & 0xf0) == 0x00 { 147 // 6.2.2 literal w/o indexing, N=4 name index. 148 let a3: i64 = hpack_decode_int(block, o, lim, 4, ibox) 149 if a3 < 0 { return a3 } 150 let nidx3: i64 = ibox[0] 151 let aval3: i64 = hpack_decode_str(block, a3, lim, sptr, slen, shuf) 152 if aval3 < 0 { return aval3 } 153 if nidx3 >= 8 { if nidx3 <= 14 { 154 if shuf[0] == 0 { 155 let st3: i64 = h2_atoi_n(block, sptr[0], slen[0]) 156 if st3 > 0 { return st3 } 157 } 158 } } 159 o = aval3 160 } else { if (lead & 0xf0) == 0x10 { 161 // 6.2.3 literal never indexed, N=4 name index. 162 let a4: i64 = hpack_decode_int(block, o, lim, 4, ibox) 163 if a4 < 0 { return a4 } 164 let nidx4: i64 = ibox[0] 165 let aval4: i64 = hpack_decode_str(block, a4, lim, sptr, slen, shuf) 166 if aval4 < 0 { return aval4 } 167 if nidx4 >= 8 { if nidx4 <= 14 { 168 if shuf[0] == 0 { 169 let st4: i64 = h2_atoi_n(block, sptr[0], slen[0]) 170 if st4 > 0 { return st4 } 171 } 172 } } 173 o = aval4 174 } else { if (lead & 0xe0) == 0x20 { 175 // 5.3 dynamic table size update (001x xxxx, N=5) -- no header, skip. 176 let a5: i64 = hpack_decode_int(block, o, lim, 5, ibox) 177 if a5 < 0 { return a5 } 178 o = a5 179 } else { 180 return 0 - 9 // unrecognized representation 181 } } } } } 182 } 183 return 0 - 8 // no :status found in block 184} 185 186// ===================================================================== 187// Walk an in-memory h2 FRAME STREAM [off,lim): find the response :status from 188// the first HEADERS block and reassemble DATA payloads into out_body. Returns 189// the body length (>=0) on success, < 0 on protocol/decode error. *out_status 190// receives the decoded :status. Pure framing -- composes h2_frame_read_header 191// (codec) + h2_decode_status (HPACK). 192// ===================================================================== 193func h2_extract_status_and_body( 194 buf: *u8, off: i64, lim: i64, 195 out_body: *u8, out_cap: i64, 196 out_status: *i64 197) -> i64 { 198 var o: i64 = off 199 var body: i64 = 0 200 var saw_headers: i64 = 0 201 out_status[0] = 0 - 1 202 let rlen: *i64 = sys_mmap(16) as *i64 203 let rtype: *i64 = sys_mmap(16) as *i64 204 let rflags: *i64 = sys_mmap(16) as *i64 205 let rsid: *i64 = sys_mmap(16) as *i64 206 while o < lim { 207 let pstart: i64 = h2_frame_read_header(buf, o, lim, rlen, rtype, rflags, rsid) 208 if pstart < 0 { return pstart } 209 let ftype: i64 = rtype[0] 210 let plen: i64 = rlen[0] 211 if ftype == 0x01 { // HEADERS 212 // payload may carry PADDED/PRIORITY but our peer never sets them; 213 // decode the whole payload as the HPACK header block. 214 if saw_headers == 0 { 215 let st: i64 = h2_decode_status(buf, pstart, pstart + plen) 216 if st < 0 { return st } 217 out_status[0] = st 218 saw_headers = 1 219 } 220 } 221 if ftype == 0x00 { // DATA 222 var i: i64 = 0 223 while i < plen { 224 if body >= out_cap { return 0 - 20 } // body overflow 225 out_body[body] = buf[pstart + i] 226 body = body + 1 227 i = i + 1 228 } 229 } 230 o = pstart + plen 231 } 232 if saw_headers == 0 { return 0 - 21 } // no response HEADERS seen 233 return body 234} 235 236// ===================================================================== 237// Send one TLS application_data record carrying `n` raw bytes (an h2 frame 238// buffer) under the session's client app key. Returns 0 ok / <0 error. 239// ===================================================================== 240func h2_tls_send(s: *Tls13ClientSession, fd: i64, payload: *u8, n: i64) -> i64 { 241 let rec: *u8 = sys_mmap(n + 64) 242 let hdr: *u8 = rec 243 let ct: *u8 = rec + NX_TLS13_RECORD_HEADER_LEN 244 let tag: *u8 = rec + NX_TLS13_RECORD_HEADER_LEN + n + 1 245 let ev: i64 = nx_tls13_record_encrypt_v2( 246 s.cipher_suite, s.client_app_traffic_key, s.client_app_iv, s.client_app_seq, 247 payload, n, NX_TLS13_CT_APPLICATION_DATA, 0, hdr, ct, tag) 248 s.client_app_seq = s.client_app_seq + 1 249 if ev != NX_TLS13_REC_VERDICT_OK { return 0 - 1 } 250 let total: i64 = NX_TLS13_RECORD_HEADER_LEN + n + 1 + NX_TLS13_RECORD_TAG_LEN 251 var w: i64 = 0 252 while w < total { 253 let k: i64 = sys_write(fd, (rec as i64 + w) as *u8, total - w) 254 if k <= 0 { return 0 - 2 } 255 w = w + k 256 } 257 return 0 258} 259 260// ===================================================================== 261// Scan complete frames in [0,lim) and return 1 iff a HEADERS or DATA frame on 262// stream 1 carries END_STREAM (flag bit 0x01) -- i.e. the response to OUR GET is 263// complete. HTTP/2 connections are PERSISTENT (the server never EOFs after one 264// response), so the read loop MUST stop on END_STREAM, not on connection close. 265// A partial trailing frame (not yet fully buffered) is ignored (read header 266// returns < 0 -> stop scanning, wait for more bytes). Returns 0 if not ended. 267// ===================================================================== 268func h2_stream1_ended(buf: *u8, lim: i64) -> i64 { 269 var o: i64 = 0 270 let rlen: *i64 = sys_mmap(16) as *i64 271 let rtype: *i64 = sys_mmap(16) as *i64 272 let rflags: *i64 = sys_mmap(16) as *i64 273 let rsid: *i64 = sys_mmap(16) as *i64 274 while o < lim { 275 let pstart: i64 = h2_frame_read_header(buf, o, lim, rlen, rtype, rflags, rsid) 276 if pstart < 0 { return 0 } // incomplete trailing frame -> not ended yet 277 let ftype: i64 = rtype[0] 278 let flags: i64 = rflags[0] 279 let sid: i64 = rsid[0] 280 let plen: i64 = rlen[0] 281 if sid == 1 { 282 if ftype == 0x00 { if (flags & 0x01) == 0x01 { return 1 } } // DATA END_STREAM 283 if ftype == 0x01 { if (flags & 0x01) == 0x01 { return 1 } } // HEADERS END_STREAM (no body) 284 } 285 o = pstart + plen 286 } 287 return 0 288} 289 290// ===================================================================== 291// NX_H2_STAGE_CAP -- the BOUNDED staging-buffer size for the streaming drain in 292// h2_tls_get. Derivation (rule 11, no magic number): 293// - a decrypted TLS 1.3 record's plaintext is <= 16645 (max record overhead); 294// - a single h2 DATA/HEADERS/SETTINGS frame from a conformant peer is bounded 295// by SETTINGS_MAX_FRAME_SIZE, default 16384 (+9 header = 16393); 296// - we stage at most ONE freshly-decrypted record appended onto a COMPACTED 297// tail (one partial straddling frame, < 16393). 16384 + 16645 + headroom 298// fits comfortably under 65536, so the stage NEVER grows past this bound no 299// matter how large the total page is (a 300KB body drains through it in 300// ~20 record-sized passes, each consumed+compacted before the next append). 301// This is what makes the drain MEMORY-BOUNDED while handling arbitrarily large 302// responses (rule-3 root fix: stream complete frames, do not buffer the page). 303// ===================================================================== 304const NX_H2_STAGE_CAP: i64 = 65536 305 306// ===================================================================== 307// STREAMING FRAME CONSUMER -- the generalization of h2_extract_status_and_body 308// (187-228) + h2_stream1_ended (262-282) into ONE pass that drains every 309// COMPLETE frame from the FRONT of a staging buffer `frames` (currently holding 310// fbytes_box[0] bytes), then COMPACTS the unconsumed tail down to the front so 311// the buffer stays bounded. Called repeatedly as TLS records arrive: each call 312// consumes whatever whole frames are now present and leaves any partial trailing 313// frame staged for the next record (the verified h2_frame_read_header contract -- 314// returns < 0 until pstart+plen <= lim -- makes the partial/straddling case exact). 315// 316// PRESERVES every feature of the two functions it replaces in the live path: 317// - :status decoded ONCE from the first HEADERS block via h2_decode_status; 318// a malformed block returns its NEGATIVE code (never a fabricated status). 319// - DATA payloads COUNTED into total_body_box (UNBOUNDED true page size) and 320// SAMPLED into out_body up to out_cap (body never truncates the fetch -- 321// rule 25: handles MORE, arbitrarily large pages, by sampling not retaining). 322// - END_STREAM on stream 1 (HEADERS or DATA, flag 0x01) -> ended_box[0]=1, 323// the persistent-connection stop condition (h2 servers do not EOF after one 324// response, see 254-261) detected INLINE during consume. 325// 326// out-params (all *i64 boxes, read-modify-write so state carries across calls): 327// fbytes_box : bytes staged (updated to the compacted remainder on return) 328// out_status : decoded :status (set once saw_headers flips to 1) 329// saw_headers : 0/1 latch -- decode :status only from the FIRST HEADERS 330// total_body : running count of ALL DATA bytes (the TRUE page length) 331// sample_box : bytes written into out_body so far (<= out_cap) 332// ended : 0/1 -- set when stream-1 END_STREAM is seen 333// Returns 0 on a clean pass, or a NEGATIVE h2_decode_status code if a header 334// block was malformed (honest fail propagated to the caller). 335// ===================================================================== 336func h2_stream_consume( 337 frames: *u8, fbytes_box: *i64, 338 out_body: *u8, out_cap: i64, out_status: *i64, 339 saw_headers_box: *i64, total_body_box: *i64, sample_box: *i64, ended_box: *i64 340) -> i64 { 341 let rlen: *i64 = sys_mmap(16) as *i64 342 let rtype: *i64 = sys_mmap(16) as *i64 343 let rflags: *i64 = sys_mmap(16) as *i64 344 let rsid: *i64 = sys_mmap(16) as *i64 345 var consumed: i64 = 0 346 let fb: i64 = fbytes_box[0] 347 while consumed < fb { 348 let pstart: i64 = h2_frame_read_header(frames, consumed, fb, rlen, rtype, rflags, rsid) 349 if pstart < 0 { consumed = consumed; break } // incomplete/straddling frame -> wait for next record 350 let ftype: i64 = rtype[0] 351 let plen: i64 = rlen[0] 352 let flags: i64 = rflags[0] 353 let sid: i64 = rsid[0] 354 if ftype == 0x01 { // HEADERS 355 if saw_headers_box[0] == 0 { 356 let st: i64 = h2_decode_status(frames, pstart, pstart + plen) 357 if st < 0 { return st } // malformed header block -> honest negative 358 out_status[0] = st 359 saw_headers_box[0] = 1 360 } 361 } 362 if ftype == 0x00 { // DATA -> count unbounded, sample to cap 363 var i: i64 = 0 364 while i < plen { 365 total_body_box[0] = total_body_box[0] + 1 366 let sl: i64 = sample_box[0] 367 if sl < out_cap { 368 out_body[sl] = frames[pstart + i] 369 sample_box[0] = sl + 1 370 } 371 i = i + 1 372 } 373 } 374 if sid == 1 { // END_STREAM only on DATA/HEADERS (rule 12: 375 if ftype == 0x00 { // an adversarial WINDOW_UPDATE/CONTINUATION 376 if (flags & 0x01) == 0x01 { ended_box[0] = 1 } // on stream 1 must NOT trip end via 377 } // a reserved 0x01 bit -> premature truncation) 378 if ftype == 0x01 { 379 if (flags & 0x01) == 0x01 { ended_box[0] = 1 } 380 } 381 } 382 consumed = pstart + plen // advance past this WHOLE frame 383 } 384 if consumed > 0 { // COMPACT tail to front -> bounded memory 385 var k: i64 = consumed // src > dst, low-to-high copy is forward-safe 386 var d: i64 = 0 387 while k < fb { 388 frames[d] = frames[k] 389 d = d + 1 390 k = k + 1 391 } 392 fbytes_box[0] = fb - consumed 393 } 394 return 0 395} 396 397// ===================================================================== 398// THE driver: perform one HTTP/2 GET over the CONNECTED TLS session. 399// 1. build client open (preface+SETTINGS+HEADERS GET) -> send as TLS record 400// 2. send a SETTINGS-ACK (the server expects us to ack its SETTINGS) 401// 3. STREAMING drain: loop read TLS record -> decrypt -> APPEND plaintext to a 402// BOUNDED NX_H2_STAGE_CAP staging buffer, CONSUME every complete frame from 403// the front (decode :status once, count+sample DATA), COMPACT the tail. 404// Stop on stream-1 END_STREAM (h2 is persistent) / close_notify / EOF. 405// 4. report decoded :status + the TRUE total body length. 406// 407// RETURN-VALUE CONTRACT (rule 19 -- ADDITIVE capability, NOT a break): 408// This used to return "bytes written to out_body (<= out_cap)". It now 409// returns the TRUE total DATA byte count of the response, which MAY EXCEED 410// out_cap; out_body holds the SAMPLED first out_cap bytes. This is what lets 411// the client drain arbitrarily large pages (rumble > 256KB) in BOUNDED memory 412// instead of hard-failing at a fixed 256KB buffer (the root fix, rule 3 -- a 413// streaming rewrite, NOT a bump of the old 262144 constant; rule 25 -- handles 414// MORE, never less). *out_status gets the decoded :status. HONEST: any 415// failure path returns a NEGATIVE code; the status is NEVER fabricated. 416// ===================================================================== 417func h2_tls_get( 418 s: *Tls13ClientSession, fd: i64, 419 authority: *u8, alen: i64, path: *u8, plen: i64, 420 out_body: *u8, out_cap: i64, 421 out_status: *i64 422) -> i64 { 423 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { return 0 - 30 } 424 out_status[0] = 0 - 1 425 426 // ---- 1: client open (preface + empty SETTINGS + HEADERS GET) ---- 427 let open: *u8 = sys_mmap(512) 428 let openn: i64 = h2_build_client_open(open, 0, authority, alen, path, plen) 429 if openn < 0 { return 0 - 31 } 430 if h2_tls_send(s, fd, open, openn) < 0 { return 0 - 32 } 431 432 // ---- 2: SETTINGS-ACK + flow-control opening ---- 433 // Bundle SETTINGS-ACK + a big connection WINDOW_UPDATE (stream 0) + a big 434 // stream-1 WINDOW_UPDATE into one record. WITHOUT this the response stalls: 435 // h2 starts both windows at 65535, so a server (e.g. google) sends 65535 body 436 // bytes then BLOCKS on flow control waiting for our WINDOW_UPDATE while we 437 // block reading -> deadlock (no END_STREAM, no EOF). +0x3fffffff keeps each 438 // window under the 2^31-1 max (65535 + 0x3fffffff < 0x7fffffff). 439 let fcb: *u8 = sys_mmap(64) 440 var fo: i64 = h2_frame_write_settings_ack(fcb, 0) 441 fo = h2_frame_write_window_update(fcb, fo, 0, 0x3fffffff) // connection window 442 fo = h2_frame_write_window_update(fcb, fo, 1, 0x3fffffff) // stream 1 window 443 if h2_tls_send(s, fd, fcb, fo) < 0 { return 0 - 33 } 444 445 // ---- 3: STREAMING drain -- bounded staging, consume-then-compact ---- 446 // `frames` is a BOUNDED NX_H2_STAGE_CAP buffer, NOT the whole page. Each 447 // decrypted record is appended, every complete frame is consumed (status 448 // decoded once, DATA counted into total_body + sampled into out_body), and 449 // the unconsumed tail is compacted to the front -- so the page may be any 450 // size while memory stays bounded. All state lives in i64 boxes that carry 451 // across the per-record h2_stream_consume passes. 452 let frames: *u8 = sys_mmap(NX_H2_STAGE_CAP) 453 let fbytes_box: *i64 = sys_mmap(16) as *i64; fbytes_box[0] = 0 // staged byte count 454 let saw_headers_box: *i64 = sys_mmap(16) as *i64; saw_headers_box[0] = 0 455 let total_body_box: *i64 = sys_mmap(16) as *i64; total_body_box[0] = 0 // TRUE page size 456 let sample_box: *i64 = sys_mmap(16) as *i64; sample_box[0] = 0 // bytes put in out_body 457 let ended_box: *i64 = sys_mmap(16) as *i64; ended_box[0] = 0 458 var done: i64 = 0 459 var budget: i64 = 0 460 while done == 0 { 461 if budget >= NX_H2_STAGE_CAP { done = 1 } // honest record-count safety cap (>256KB ~ 20 records) 462 budget = budget + 1 463 let rec_in: *u8 = sys_mmap(NX_MAGIC_16645) 464 let rtot: i64 = nx_tls13_read_record_from_fd(fd, rec_in, NX_MAGIC_16645) 465 if rtot < 0 { 466 let nv: i64 = 0 - rtot 467 if nv == NX_TLS13_READ_REC_EOF { done = 1 } 468 else { if nv == NX_TLS13_READ_REC_PAYLOAD_EOF { done = 1 } 469 else { return 0 - 34 } } 470 } 471 if rtot > 0 { 472 let h: *u8 = rec_in 473 let ct: *u8 = rec_in + NX_TLS13_RECORD_HEADER_LEN 474 let ctlen: i64 = rtot - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN 475 let tag: *u8 = rec_in + rtot - NX_TLS13_RECORD_TAG_LEN 476 let pt: *u8 = sys_mmap(ctlen + 16) 477 let ptct: *i64 = sys_mmap(16) as *i64 478 let ptlen: *i64 = sys_mmap(16) as *i64 479 let dv: i64 = nx_tls13_record_decrypt_v2( 480 s.cipher_suite, s.server_app_traffic_key, s.server_app_iv, s.server_app_seq, 481 h, ct, ctlen, tag, pt, ptct, ptlen) 482 s.server_app_seq = s.server_app_seq + 1 483 if dv != NX_TLS13_REC_VERDICT_OK { return 0 - 35 } 484 if ptct[0] == NX_TLS13_CT_ALERT { done = 1 } 485 if ptct[0] == NX_TLS13_CT_APPLICATION_DATA { 486 // If this record will not fit on the staged tail, drain first to 487 // free room (the consume step usually empties stage to near-zero). 488 if fbytes_box[0] + ptlen[0] > NX_H2_STAGE_CAP { 489 let cv0: i64 = h2_stream_consume(frames, fbytes_box, out_body, out_cap, out_status, saw_headers_box, total_body_box, sample_box, ended_box) 490 if cv0 < 0 { return cv0 } 491 // Still no room after compaction -> a single frame larger than 492 // the staging buffer. Cannot happen with the default 16384 493 // max-frame, but we return an HONEST negative, never corrupt. 494 if fbytes_box[0] + ptlen[0] > NX_H2_STAGE_CAP { return 0 - 38 } 495 } 496 // APPEND this record's plaintext onto the staged tail. 497 var j: i64 = 0 498 while j < ptlen[0] { 499 frames[fbytes_box[0] + j] = pt[j] 500 j = j + 1 501 } 502 fbytes_box[0] = fbytes_box[0] + ptlen[0] 503 // CONSUME complete frames + COMPACT; END_STREAM detected inline. 504 let cv: i64 = h2_stream_consume(frames, fbytes_box, out_body, out_cap, out_status, saw_headers_box, total_body_box, sample_box, ended_box) 505 if cv < 0 { return cv } 506 if ended_box[0] == 1 { done = 1 } // stream 1 complete -> stop (persistent conn) 507 } 508 } 509 } 510 511 // ---- 4: report :status + TRUE total body length ---- 512 // out_status[0] was already set by h2_stream_consume on the first HEADERS. 513 if saw_headers_box[0] == 0 { return 0 - 37 } // no response HEADERS at all -> honest fail 514 return total_body_box[0] // TRUE full page size (may exceed out_cap) 515} 516 517// ===================================================================== 518// h2_tls_drain -- the post-open SETTINGS-ACK + flow-window + streaming response 519// drain, factored out so a UA-carrying GET reuses the EXACT same drain (the memory's 520// "extract drain into h2_tls_drain" plan). Identical logic to h2_tls_get steps 2-4. 521// (B)-debt: h2_tls_get still inlines its own copy; a later pass folds it onto this. 522// ===================================================================== 523func h2_tls_drain(s: *Tls13ClientSession, fd: i64, out_body: *u8, out_cap: i64, out_status: *i64) -> i64 { 524 let fcb: *u8 = sys_mmap(64) 525 var fo: i64 = h2_frame_write_settings_ack(fcb, 0) 526 fo = h2_frame_write_window_update(fcb, fo, 0, 0x3fffffff) 527 fo = h2_frame_write_window_update(fcb, fo, 1, 0x3fffffff) 528 if h2_tls_send(s, fd, fcb, fo) < 0 { return 0 - 33 } 529 530 let frames: *u8 = sys_mmap(NX_H2_STAGE_CAP) 531 let fbytes_box: *i64 = sys_mmap(16) as *i64; fbytes_box[0] = 0 532 let saw_headers_box: *i64 = sys_mmap(16) as *i64; saw_headers_box[0] = 0 533 let total_body_box: *i64 = sys_mmap(16) as *i64; total_body_box[0] = 0 534 let sample_box: *i64 = sys_mmap(16) as *i64; sample_box[0] = 0 535 let ended_box: *i64 = sys_mmap(16) as *i64; ended_box[0] = 0 536 var done: i64 = 0 537 var budget: i64 = 0 538 while done == 0 { 539 if budget >= NX_H2_STAGE_CAP { done = 1 } 540 budget = budget + 1 541 let rec_in: *u8 = sys_mmap(NX_MAGIC_16645) 542 let rtot: i64 = nx_tls13_read_record_from_fd(fd, rec_in, NX_MAGIC_16645) 543 if rtot < 0 { 544 let nv: i64 = 0 - rtot 545 if nv == NX_TLS13_READ_REC_EOF { done = 1 } 546 else { if nv == NX_TLS13_READ_REC_PAYLOAD_EOF { done = 1 } 547 else { return 0 - 34 } } 548 } 549 if rtot > 0 { 550 let h: *u8 = rec_in 551 let ct: *u8 = rec_in + NX_TLS13_RECORD_HEADER_LEN 552 let ctlen: i64 = rtot - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN 553 let tag: *u8 = rec_in + rtot - NX_TLS13_RECORD_TAG_LEN 554 let pt: *u8 = sys_mmap(ctlen + 16) 555 let ptct: *i64 = sys_mmap(16) as *i64 556 let ptlen: *i64 = sys_mmap(16) as *i64 557 let dv: i64 = nx_tls13_record_decrypt_v2( 558 s.cipher_suite, s.server_app_traffic_key, s.server_app_iv, s.server_app_seq, 559 h, ct, ctlen, tag, pt, ptct, ptlen) 560 s.server_app_seq = s.server_app_seq + 1 561 if dv != NX_TLS13_REC_VERDICT_OK { return 0 - 35 } 562 if ptct[0] == NX_TLS13_CT_ALERT { done = 1 } 563 if ptct[0] == NX_TLS13_CT_APPLICATION_DATA { 564 if fbytes_box[0] + ptlen[0] > NX_H2_STAGE_CAP { 565 let cv0: i64 = h2_stream_consume(frames, fbytes_box, out_body, out_cap, out_status, saw_headers_box, total_body_box, sample_box, ended_box) 566 if cv0 < 0 { return cv0 } 567 if fbytes_box[0] + ptlen[0] > NX_H2_STAGE_CAP { return 0 - 38 } 568 } 569 var j: i64 = 0 570 while j < ptlen[0] { 571 frames[fbytes_box[0] + j] = pt[j] 572 j = j + 1 573 } 574 fbytes_box[0] = fbytes_box[0] + ptlen[0] 575 let cv: i64 = h2_stream_consume(frames, fbytes_box, out_body, out_cap, out_status, saw_headers_box, total_body_box, sample_box, ended_box) 576 if cv < 0 { return cv } 577 if ended_box[0] == 1 { done = 1 } 578 } 579 } 580 } 581 if saw_headers_box[0] == 0 { return 0 - 37 } 582 return total_body_box[0] 583} 584 585// h2_tls_get_ua -- same as h2_tls_get but sends an identified desktop User-Agent 586// (so origins that JS-wall anonymous clients serve the real results HTML). ADDITIVE. 587func h2_tls_get_ua( 588 s: *Tls13ClientSession, fd: i64, 589 authority: *u8, alen: i64, path: *u8, plen: i64, 590 ua: *u8, ualen: i64, 591 out_body: *u8, out_cap: i64, 592 out_status: *i64 593) -> i64 { 594 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { return 0 - 30 } 595 out_status[0] = 0 - 1 596 let open: *u8 = sys_mmap(NX_MAGIC_2048) 597 let openn: i64 = h2_build_client_open_ua(open, 0, authority, alen, path, plen, ua, ualen) 598 if openn < 0 { return 0 - 31 } 599 if h2_tls_send(s, fd, open, openn) < 0 { return 0 - 32 } 600 return h2_tls_drain(s, fd, out_body, out_cap, out_status) 601} 602 603// ===================================================================== 604// GATE (main): synthesize a server h2 response with OUR builders and assert the 605// extractor recovers :status + body byte-exact, plus tamper (404 not coerced, 606// empty body not fabricated). In-memory only -- no sockets in the gate (the live 607// network proof is the authored fetch organ's guarded GET). 608// ===================================================================== 609func hc_synth_response(resp: *u8, status_byte: i64, body: *u8, blen: i64, with_data: i64) -> i64 { 610 var ro: i64 = 0 611 ro = h2_frame_write_settings_empty(resp, ro) // peer SETTINGS 612 ro = h2_frame_write_settings_ack(resp, ro) // SETTINGS-ACK 613 let hblk: *u8 = sys_mmap(16); hblk[0] = status_byte as u8 // indexed :status 614 var hflags: i64 = 0x04 // END_HEADERS 615 if with_data == 0 { hflags = 0x05 } // END_HEADERS|END_STREAM (no body) 616 ro = h2_frame_write_headers(resp, ro, 1, hflags, hblk, 1) 617 if with_data == 1 { 618 ro = h2_frame_write_data(resp, ro, 1, 0x01, body, blen) // DATA END_STREAM 619 } 620 return ro 621} 622 623func main() -> i64 { 624 var pass: i64 = 0 625 var tot: i64 = 0 626 hc_puts("nx_h2_client_over_tls gate (RFC 9113/7541, FOUNDED on h2_conformance+tls13_record)\n" as *u8) 627 628 // ---- KAT 1: :status 200 (0x88) + body "hello h2" ---- 629 let body1: *u8 = sys_mmap(16) 630 body1[0]=104 as u8; body1[1]=101 as u8; body1[2]=108 as u8; body1[3]=108 as u8 631 body1[4]=111 as u8; body1[5]=32 as u8; body1[6]=104 as u8; body1[7]=50 as u8 // "hello h2" 632 let resp1: *u8 = sys_mmap(256) 633 let r1: i64 = hc_synth_response(resp1, 0x88, body1, 8, 1) 634 let ob1: *u8 = sys_mmap(256) 635 let st1: *i64 = sys_mmap(16) as *i64 636 let bl1: i64 = h2_extract_status_and_body(resp1, 0, r1, ob1, 256, st1) 637 var k1: i64 = 0 638 if st1[0] == 200 { if bl1 == 8 { 639 var m: i64 = 0; var ok: i64 = 1 640 while m < 8 { if (ob1[m] & 0xff) != (body1[m] & 0xff) { ok = 0 } m = m + 1 } 641 if ok == 1 { k1 = 1 } 642 } } 643 if k1 == 1 { hc_puts(" PASS :status 200 + body 'hello h2' (8 bytes) recovered\n" as *u8); pass = pass + 1 } 644 if k1 == 0 { hc_puts(" FAIL :status 200 path: status=" as *u8); hc_putn(st1[0]); hc_puts(" body_len=" as *u8); hc_putn(bl1); hc_puts("\n" as *u8) } 645 tot = tot + 1 646 647 // ---- KAT 2: HPACK :status via literal-indexed-name 8 with value "200" ---- 648 // the lit-inc-indexed-name form a server may use: 0x48 ('01'+name idx 8) 649 // then str "200". build it with the encoder + decode it. 650 let lblk: *u8 = sys_mmap(32) 651 let lv: *u8 = sys_mmap(8); lv[0]=50 as u8; lv[1]=48 as u8; lv[2]=48 as u8 // "200" 652 let ln: i64 = hpack_encode_lit_inc_indexed_name(lblk, 0, 8, lv, 3) 653 let resp2: *u8 = sys_mmap(128) 654 var r2o: i64 = h2_frame_write_headers(resp2, 0, 1, 0x05, lblk, ln) 655 let st2: *i64 = sys_mmap(16) as *i64 656 let ob2: *u8 = sys_mmap(64) 657 let bl2: i64 = h2_extract_status_and_body(resp2, 0, r2o, ob2, 64, st2) 658 var k2: i64 = 0 659 if st2[0] == 200 { if bl2 == 0 { k2 = 1 } } 660 if k2 == 1 { hc_puts(" PASS :status via literal-indexed-name 8 -> 200, empty body\n" as *u8); pass = pass + 1 } 661 if k2 == 0 { hc_puts(" FAIL literal :status: status=" as *u8); hc_putn(st2[0]); hc_puts(" body_len=" as *u8); hc_putn(bl2); hc_puts("\n" as *u8) } 662 tot = tot + 1 663 664 // ---- TAMPER 1 (separate buffer): :status 404 (0x8d) must recover 404, NOT 200 ---- 665 let resp3: *u8 = sys_mmap(128) 666 let r3: i64 = hc_synth_response(resp3, 0x8d, body1, 0, 0) // 404, END_STREAM, no body 667 let st3: *i64 = sys_mmap(16) as *i64 668 let ob3: *u8 = sys_mmap(64) 669 let bl3: i64 = h2_extract_status_and_body(resp3, 0, r3, ob3, 64, st3) 670 var k3: i64 = 0 671 if st3[0] == 404 { if bl3 == 0 { k3 = 1 } } 672 if k3 == 1 { hc_puts(" PASS tamper :status 404 recovered as 404 (not coerced to 200), empty body\n" as *u8); pass = pass + 1 } 673 if k3 == 0 { hc_puts(" FAIL tamper 404: status=" as *u8); hc_putn(st3[0]); hc_puts(" body_len=" as *u8); hc_putn(bl3); hc_puts("\n" as *u8) } 674 tot = tot + 1 675 676 // ---- TAMPER 2: a frame stream with NO HEADERS (only DATA) must FAIL (<0), 677 // never invent a status. Separate buffer. ---- 678 let resp4: *u8 = sys_mmap(64) 679 let r4: i64 = h2_frame_write_data(resp4, 0, 1, 0x01, body1, 4) 680 let st4: *i64 = sys_mmap(16) as *i64 681 let ob4: *u8 = sys_mmap(64) 682 let bl4: i64 = h2_extract_status_and_body(resp4, 0, r4, ob4, 64, st4) 683 var k4: i64 = 0 684 if bl4 < 0 { if st4[0] < 0 { k4 = 1 } } 685 if k4 == 1 { hc_puts(" PASS tamper DATA-without-HEADERS rejected (rc=" as *u8); hc_putn(bl4); hc_puts(", no fabricated status)\n" as *u8); pass = pass + 1 } 686 if k4 == 0 { hc_puts(" FAIL tamper no-HEADERS: rc=" as *u8); hc_putn(bl4); hc_puts(" status=" as *u8); hc_putn(st4[0]); hc_puts("\n" as *u8) } 687 tot = tot + 1 688 689 // ================================================================= 690 // KAT 5 (THE rung): a >256KB body MUST drain through the BOUNDED streaming 691 // consumer in BOUNDED memory. This drives the SAME h2_stream_consume the 692 // live h2_tls_get path uses. Synthesize HEADERS(:status 200, END_HEADERS, 693 // NOT end_stream) + many DATA frames (each 8192 payload) totaling EXACTLY 694 // 300000, last DATA END_STREAM. Then feed the synthesized bytes into a 695 // 65536 staging buffer in 16384-byte record-sized SLICES, calling 696 // h2_stream_consume after each appended slice, and assert the stage NEVER 697 // grew past NX_H2_STAGE_CAP at any point. 300000 >> 65536 and >> the old 698 // 262144 wall -> proves the 256KB hard-fail is gone (rule 3 root fix). 699 // ================================================================= 700 let BIG_TOTAL: i64 = NX_MAGIC_300000 701 let CHUNK: i64 = NX_MAGIC_8192 // per-DATA-frame payload (< default NX_MAGIC_16384 max-frame) 702 // Build the full synthesized frame stream into a scratch SOURCE buffer (this 703 // big buffer is the *server's wire output*, NOT our staging -- the whole point 704 // is that our stage stays bounded while the source is arbitrarily large). 705 let srccap: i64 = BIG_TOTAL + NX_MAGIC_4096 706 let src: *u8 = sys_mmap(srccap) 707 let hblk5: *u8 = sys_mmap(16); hblk5[0] = 0x88 as u8 // indexed :status 200 708 var so: i64 = h2_frame_write_headers(src, 0, 1, 0x04, hblk5, 1) // END_HEADERS, NOT end_stream 709 let dpay: *u8 = sys_mmap(CHUNK + 16) 710 var fillv: i64 = 0 711 while fillv < CHUNK { dpay[fillv] = ((fillv & 0xff)) as u8; fillv = fillv + 1 } // deterministic bytes 712 var remaining: i64 = BIG_TOTAL 713 while remaining > 0 { 714 var seg: i64 = CHUNK 715 if remaining < CHUNK { seg = remaining } 716 var dflags: i64 = 0x00 717 if (remaining - seg) == 0 { dflags = 0x01 } // last DATA -> END_STREAM 718 so = h2_frame_write_data(src, so, 1, dflags, dpay, seg) 719 remaining = remaining - seg 720 } 721 let srclen: i64 = so // total wire bytes (~300KB + framing) 722 723 // Drain `src` through a BOUNDED stage in 16384-byte slices. 724 let stage: *u8 = sys_mmap(NX_H2_STAGE_CAP) 725 let s_fb: *i64 = sys_mmap(16) as *i64; s_fb[0] = 0 726 let s_sh: *i64 = sys_mmap(16) as *i64; s_sh[0] = 0 727 let s_tb: *i64 = sys_mmap(16) as *i64; s_tb[0] = 0 728 let s_sm: *i64 = sys_mmap(16) as *i64; s_sm[0] = 0 729 let s_en: *i64 = sys_mmap(16) as *i64; s_en[0] = 0 730 let s_st: *i64 = sys_mmap(16) as *i64; s_st[0] = 0 - 1 731 let ob5cap: i64 = NX_MAGIC_4096 // out_body SAMPLE cap << BIG_TOTAL 732 let ob5: *u8 = sys_mmap(ob5cap) 733 let SLICE: i64 = NX_MAGIC_16384 734 var max_staged: i64 = 0 735 var overflow5: i64 = 0 736 var consume_err5: i64 = 0 737 var pos: i64 = 0 738 while pos < srclen { 739 var n: i64 = SLICE 740 if (srclen - pos) < SLICE { n = srclen - pos } 741 // Same append-then-consume discipline as the live drain (boundary guard). 742 if s_fb[0] + n > NX_H2_STAGE_CAP { 743 let cvx: i64 = h2_stream_consume(stage, s_fb, ob5, ob5cap, s_st, s_sh, s_tb, s_sm, s_en) 744 if cvx < 0 { consume_err5 = 1 } 745 if s_fb[0] + n > NX_H2_STAGE_CAP { overflow5 = 1 } 746 } 747 if overflow5 == 0 { 748 var c: i64 = 0 749 while c < n { stage[s_fb[0] + c] = src[pos + c]; c = c + 1 } 750 s_fb[0] = s_fb[0] + n 751 if s_fb[0] > max_staged { max_staged = s_fb[0] } // track peak BEFORE consume 752 let cv5: i64 = h2_stream_consume(stage, s_fb, ob5, ob5cap, s_st, s_sh, s_tb, s_sm, s_en) 753 if cv5 < 0 { consume_err5 = 1 } 754 } 755 pos = pos + n 756 } 757 var k5: i64 = 0 758 if s_st[0] == 200 { if s_tb[0] == BIG_TOTAL { if s_en[0] == 1 { if s_sh[0] == 1 { 759 if max_staged <= NX_H2_STAGE_CAP { if consume_err5 == 0 { if overflow5 == 0 { 760 // out_body holds exactly the first ob5cap sampled bytes, byte-exact. 761 var so_ok: i64 = 1; var q: i64 = 0 762 while q < ob5cap { if (ob5[q] & 0xff) != ((q & 0xff)) { so_ok = 0 } q = q + 1 } 763 if so_ok == 1 { k5 = 1 } 764 } } } 765 } } } } 766 if k5 == 1 { 767 hc_puts(" PASS streaming >256KB drained in bounded memory: total_body=" as *u8); hc_putn(s_tb[0]) 768 hc_puts(" (>" as *u8); hc_putn(NX_H2_STAGE_CAP); hc_puts(" stage) max_staged=" as *u8); hc_putn(max_staged) 769 hc_puts(" status=200 ended=1 sample[" as *u8); hc_putn(ob5cap); hc_puts("]=byte-exact\n" as *u8) 770 pass = pass + 1 771 } 772 if k5 == 0 { 773 hc_puts(" FAIL streaming >256KB: status=" as *u8); hc_putn(s_st[0]) 774 hc_puts(" total_body=" as *u8); hc_putn(s_tb[0]); hc_puts(" ended=" as *u8); hc_putn(s_en[0]) 775 hc_puts(" saw_headers=" as *u8); hc_putn(s_sh[0]); hc_puts(" max_staged=" as *u8); hc_putn(max_staged) 776 hc_puts(" consume_err=" as *u8); hc_putn(consume_err5); hc_puts(" overflow=" as *u8); hc_putn(overflow5); hc_puts("\n" as *u8) 777 } 778 tot = tot + 1 779 780 // ================================================================= 781 // KAT 6 (defensive RED, honest -- NEVER faked): a single DATA frame whose 782 // declared payload EXCEEDS NX_H2_STAGE_CAP-9 cannot be staged. Drive the 783 // EXACT append-overflow discipline of h2_tls_get and assert it would return 784 // 0-38 (single-frame-too-big), NOT a fabricated 200. We synthesize ONLY the 785 // 9-byte header declaring a jumbo Length (we never need its payload bytes -- 786 // the overflow fires on the declared length before any append completes). 787 // ================================================================= 788 let jumbo_plen: i64 = NX_H2_STAGE_CAP - 8 // = STAGE_CAP-9 + 1, just over the limit 789 let jhdr: *u8 = sys_mmap(16) 790 // write a DATA frame header (type 0x00) on stream 1 declaring jumbo_plen. 791 let jn: i64 = h2_frame_write_header(jhdr, 0, jumbo_plen, 0x00, 0x01, 1) 792 // simulate the live append: stage is empty, "record" = the 9 header bytes + 793 // (pretend) jumbo payload. ptlen for the record = 9 + jumbo_plen. 794 let jstage: *u8 = sys_mmap(NX_H2_STAGE_CAP) 795 let j_fb: *i64 = sys_mmap(16) as *i64; j_fb[0] = 0 796 let j_sh: *i64 = sys_mmap(16) as *i64; j_sh[0] = 0 797 let j_tb: *i64 = sys_mmap(16) as *i64; j_tb[0] = 0 798 let j_sm: *i64 = sys_mmap(16) as *i64; j_sm[0] = 0 799 let j_en: *i64 = sys_mmap(16) as *i64; j_en[0] = 0 800 let j_st: *i64 = sys_mmap(16) as *i64; j_st[0] = 0 - 1 801 let j_record_len: i64 = 9 + jumbo_plen // the full frame the peer "sent" 802 var rc6: i64 = 0 // 0 = no decision, 0-38 = honest reject 803 if j_fb[0] + j_record_len > NX_H2_STAGE_CAP { 804 let jcv: i64 = h2_stream_consume(jstage, j_fb, ob5, ob5cap, j_st, j_sh, j_tb, j_sm, j_en) 805 if jcv < 0 { rc6 = jcv } 806 if j_fb[0] + j_record_len > NX_H2_STAGE_CAP { rc6 = 0 - 38 } 807 } 808 var k6: i64 = 0 809 if rc6 == (0 - 38) { if j_st[0] == (0 - 1) { if jn > 0 { k6 = 1 } } } // honest RED, status NOT fabricated 810 if k6 == 1 { hc_puts(" PASS single-frame-too-big rejected rc=-38 (declared plen=" as *u8); hc_putn(jumbo_plen); hc_puts(" > stage-9), status NOT fabricated\n" as *u8); pass = pass + 1 } 811 if k6 == 0 { hc_puts(" FAIL jumbo-frame: rc=" as *u8); hc_putn(rc6); hc_puts(" status=" as *u8); hc_putn(j_st[0]); hc_puts("\n" as *u8) } 812 tot = tot + 1 813 814 hc_puts("---- h2_client_over_tls gate: passed " as *u8); hc_putn(pass); hc_puts(" / " as *u8); hc_putn(tot); hc_puts("\n" as *u8) 815 if pass == tot { 816 let lfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_client_over_tls.log" as *u8, 0x1a4) 817 if lfd >= 0 { 818 sys_write(lfd, "R4-H2-CLIENT-GATE organ=nx_h2_client_over_tls kats=" as *u8, 51) 819 hc_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); hc_fdn(lfd, tot) 820 sys_write(lfd, " tamper=ok verdict=GREEN\n" as *u8, 25) 821 sys_close(lfd) 822 } 823 sys_exit(0) 824 } 825 let rfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_client_over_tls.log" as *u8, 0x1a4) 826 if rfd >= 0 { 827 sys_write(rfd, "R4-H2-CLIENT-GATE organ=nx_h2_client_over_tls kats=" as *u8, 51) 828 hc_fdn(rfd, pass); sys_write(rfd, "/" as *u8, 1); hc_fdn(rfd, tot) 829 sys_write(rfd, " tamper=?? verdict=RED\n" as *u8, 23) 830 sys_close(rfd) 831 } 832 sys_exit(1) 833 return 0 834}