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}