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}