nx_h2_flow.nx source
↩ module page · 426 lines · 25567 B
1// nx_h2_flow.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under the R4-H2
2// HTTP/2-transport-ladder workflow), NOT credited as team self-authoring.
3//
4// R4-H2-005 of the sovereign HTTP/2 transport ladder
5// (knowledge/specs/2026-06-13-http2-transport-ladder.md). HTTP/2 flow control
6// (RFC 9113 §5.2 / §6.9). Like the stream state machine this organ emits almost
7// no wire bytes -- it is the WINDOW ARITHMETIC the RFC dictates: a connection
8// window (stream 0) and a per-stream window each initialised to
9// SETTINGS_INITIAL_WINDOW_SIZE (default 65535), debited by the ENTIRE payload of
10// every DATA frame (and ONLY DATA frames, §5.2.1), replenished by WINDOW_UPDATE
11// increments, with the hard ceiling 2^31-1. The VALUE SEMANTICS are an allowed
12// internet-boundary requirement (a peer that disagrees about when a window is
13// exhausted or overflowed cannot interoperate, exactly as it must agree on the
14// frame bytes); the IMPLEMENTATION is pure NishiLang via nx_cc -> nxasm_x86:
15// no gcc, no openssl, no nghttp2.
16//
17// FOUNDED ON (composes, does not reinvent -- anti-orphan law):
18// - nx_h2_frame.nx (R4-H2-003, GREEN): imported EXACTLY ONCE. It transitively
19// splices nx_hpack.nx -> nx_str.nx -> syscalls.nx, so we inherit sys_mmap /
20// sys_write / sys_exit / sys_openat_append / sys_close from that single
21// import (importing nx_hpack / nx_str / nx_syscalls / nx_tls13 directly TOO
22// would be the RC6 double-import landmine). The WINDOW_UPDATE builder
23// h2_frame_write_window_update (§6.9, type 0x08, 4-octet payload), the
24// big-endian helpers, and the SETTINGS INITIAL_WINDOW_SIZE param emitter all
25// live in nx_h2_frame.nx -- this rung emits the SAME WINDOW_UPDATE bytes the
26// codec does (single source of truth), it does NOT re-roll its own builder.
27// No floating capability: the frame codec founds this flow-control layer; the
28// conformance + ALPN-h2 rung (R4-H2-006) founds on this next.
29//
30// BACK-FILL: the team RE-AUTHORS this from the DATA spec via the
31// emitter-of-emitters (X-AUT-006c/e/f) -- this hand-authored scaffold is the
32// sanctioned one-time bootstrap only (meter-integrity, mirror
33// nx_frame_codec.nx:13-16 / nx_h2_frame.nx:31-34 / nx_h2_stream.nx:27-30).
34//
35// GATE (main): asserts the RFC 9113 §6.9 known-answers byte-exact AND the window
36// arithmetic. WINDOW_UPDATE +65535 on the connection (stream 0) and on stream 1,
37// WINDOW_UPDATE +2147483647 (max) on stream 1, the SETTINGS INITIAL_WINDOW_SIZE
38// =65535 frame -- all built by the composed nx_h2_frame builders. Value math:
39// init -> 65535; consume(65535, 100) -> 65435; apply_window_update(0, 65535) ->
40// 65535; is_data_frame(DATA 0x00)==1 / (HEADERS 0x01)==0; apply_settings_initial
41// (65535, old 65535, new 0) -> -65535 (negative legal, §6.9.2). PLUS TAMPER /
42// illegal cases on SEPARATE window scratch values (the canonical KAT windows are
43// never entangled with the rejection path, defect-(c) discipline): WINDOW_UPDATE
44// increment 0 -> -PROTOCOL_ERROR (0x01); window raised above 2^31-1 (0x7FFFFFFF
45// + 1) -> -FLOW_CONTROL_ERROR (0x03); consume(50, 100) overdraw ->
46// -FLOW_CONTROL_ERROR; h2_window_update_length_valid(5) -> 0 (FRAME_SIZE_ERROR).
47//
48// license_tier: INDEPENDENT_REDERIVE
49// genealogy_id: international-research-sources/ietf/rfc_9113
50// lineage_id: nishi_h2_flow_r4h2_005
51
52import "nx_h2_frame.nx"
53import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
54const K_MAGIC_65535: i64 = 65535
55const K_MAGIC_2147483647: i64 = 2147483647
56const K_MAGIC_65435: i64 = 65435
57const K_MAGIC_130970: i64 = 130970
58const K_MAGIC_100000: i64 = 100000
59const K_MAGIC_34565: i64 = 34565
60
61// =====================================================================
62// RFC 9113 §6.9 constants. Default and ceiling are wire-boundary values; the
63// error codes are RFC 9113 §7. These are NOT magic numbers -- they are the
64// RFC's named constants (the wire boundary), surfaced as readable funcs.
65// =====================================================================
66// H2_INITIAL_WINDOW_DEFAULT 65535 (§6.9.2, SETTINGS_INITIAL_WINDOW_SIZE default)
67// H2_MAX_WINDOW 2147483647 (2^31-1 = 0x7FFFFFFF, the flow-control ceiling)
68// H2_ERR_PROTOCOL_ERROR 0x01 (§7: zero WINDOW_UPDATE increment)
69// H2_ERR_FLOW_CONTROL_ERROR 0x03 (§7: overflow / overdraw)
70func h2_flow_initial_window_default() -> i64 { return K_MAGIC_65535 }
71func h2_flow_max_window() -> i64 { return K_MAGIC_2147483647 }
72func h2_flow_err_protocol() -> i64 { return 0x01 }
73func h2_flow_err_flow_control() -> i64 { return 0x03 }
74
75// =====================================================================
76// §6.9.2 A new window starts at SETTINGS_INITIAL_WINDOW_SIZE. Both the
77// connection (stream 0) window and every per-stream window are initialised the
78// same way (default 65535); the caller passes the *i64 cell to seed.
79// =====================================================================
80func h2_flow_init(window: *i64) -> i64 {
81 *window = h2_flow_initial_window_default()
82 return *window
83}
84
85// Seed a window to an explicit initial value (used when SETTINGS has already
86// negotiated a non-default SETTINGS_INITIAL_WINDOW_SIZE before the stream opens).
87// A negotiated initial above the 2^31-1 ceiling is a connection-level
88// FLOW_CONTROL_ERROR (§6.9.2); reject it rather than seed an illegal window.
89func h2_flow_init_to(window: *i64, initial: i64) -> i64 {
90 if initial < 0 { return 0 - h2_flow_err_flow_control() }
91 if initial > h2_flow_max_window() { return 0 - h2_flow_err_flow_control() }
92 *window = initial
93 return *window
94}
95
96// =====================================================================
97// §5.2.1 DATA consumes window. The window is debited by the ENTIRE DATA
98// payload (including any Pad Length octet + padding -- the caller passes the
99// full payload length). If the available window is smaller than the payload the
100// sender MUST NOT send it: this is a flow-control overdraw -> FLOW_CONTROL_ERROR
101// (§6.9). On success the window is reduced and the NEW value returned.
102// =====================================================================
103func h2_flow_consume(window: *i64, data_len: i64) -> i64 {
104 if data_len < 0 { return 0 - h2_flow_err_flow_control() }
105 if data_len > *window { return 0 - h2_flow_err_flow_control() } // overdraw rejected
106 *window = *window - data_len
107 return *window
108}
109
110// =====================================================================
111// §6.9 WINDOW_UPDATE replenishes the window by `increment`.
112// increment == 0 -> PROTOCOL_ERROR (§6.9: a 0 increment is illegal)
113// *window + increment > 2^31-1 -> FLOW_CONTROL_ERROR (§6.9: exceeding the
114// ceiling is a flow-control error)
115// A negative increment is not representable on the 31-bit wire field, but guard
116// it defensively (treat as PROTOCOL_ERROR). On success the window grows and the
117// NEW value is returned.
118// =====================================================================
119func h2_flow_apply_window_update(window: *i64, increment: i64) -> i64 {
120 if increment <= 0 { return 0 - h2_flow_err_protocol() } // zero/neg increment illegal
121 // overflow check BEFORE mutating: compare against the ceiling without
122 // overflowing i64 (window <= 2^31-1 already, increment <= 2^31-1 on wire).
123 if increment > h2_flow_max_window() - *window {
124 return 0 - h2_flow_err_flow_control() // would exceed 2^31-1
125 }
126 *window = *window + increment
127 return *window
128}
129
130// =====================================================================
131// §6.9.2 When SETTINGS_INITIAL_WINDOW_SIZE changes, every existing STREAM
132// window is adjusted by delta = (new_initial - old_initial). This MAY drive a
133// stream window negative, which is LEGAL -- a subsequent WINDOW_UPDATE brings it
134// back positive before more DATA may flow. This adjustment applies to STREAM
135// windows ONLY; it does NOT touch the connection window. Both new and old
136// initials must be within [0, 2^31-1] (an out-of-range SETTINGS value is itself a
137// connection FLOW_CONTROL_ERROR caught when the SETTINGS frame is parsed).
138// =====================================================================
139func h2_flow_apply_settings_initial(window: *i64, old_initial: i64, new_initial: i64) -> i64 {
140 if new_initial < 0 { return 0 - h2_flow_err_flow_control() }
141 if new_initial > h2_flow_max_window() { return 0 - h2_flow_err_flow_control() }
142 if old_initial < 0 { return 0 - h2_flow_err_flow_control() }
143 if old_initial > h2_flow_max_window() { return 0 - h2_flow_err_flow_control() }
144 let delta: i64 = new_initial - old_initial
145 *window = *window + delta // may go negative -- legal (§6.9.2)
146 return *window
147}
148
149// =====================================================================
150// §5.2.1 Flow control applies ONLY to DATA frames (type 0x00); EVERY other
151// frame type consumes ZERO window. This is the gate that decides whether a
152// received/sent frame debits the windows at all.
153// =====================================================================
154func h2_flow_is_data_frame(ftype: i64) -> i64 {
155 if ftype == 0x00 { return 1 } // DATA
156 return 0
157}
158
159// §6.9 A WINDOW_UPDATE frame's payload MUST be exactly 4 octets (R(1) + Window
160// Size Increment(31)); any other length is a FRAME_SIZE_ERROR.
161func h2_window_update_length_valid(len: i64) -> i64 {
162 if len == 4 { return 1 }
163 return 0
164}
165
166// =====================================================================
167// §6.9 Emit a WINDOW_UPDATE frame. This is the COMPOSITION point: we do NOT
168// re-roll the frame layout -- we delegate to nx_h2_frame.nx's
169// h2_frame_write_window_update (type 0x08, 4-octet payload, R-bit forced 0), the
170// exact same builder the codec gate already byte-asserts. Returned: new offset.
171// =====================================================================
172func h2_flow_write_window_update(out: *u8, off: i64, stream_id: i64, increment: i64) -> i64 {
173 return h2_frame_write_window_update(out, off, stream_id, increment)
174}
175
176// =====================================================================
177// GATE (main): RFC 9113 §6.9 byte-exact KATs (composed builders) + window
178// arithmetic + tamper cases. Print helpers mirror the h2_puts/h2_putn/h2_fdn
179// pattern (renamed fl_*) -- the fd-aware decimal writer is used ONLY by the
180// durable status-line append.
181// =====================================================================
182func fl_puts(s: *u8) -> i64 {
183 var n: i64 = 0
184 while s[n] != (0 as u8) { n = n + 1 }
185 sys_write(1, s, n)
186 return 0
187}
188// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
189// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
190// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
191// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
192func fl_putn(v: i64) -> i64 { nxi_out(v); return 0 }
193func fl_puthex2(v: i64) -> i64 {
194 let h: *u8 = sys_mmap(4)
195 let d0: i64 = (v >> 4) & 0xf
196 let d1: i64 = v & 0xf
197 if d0 < 10 { h[0] = (48 + d0) as u8 } else { h[0] = (87 + d0) as u8 }
198 if d1 < 10 { h[1] = (48 + d1) as u8 } else { h[1] = (87 + d1) as u8 }
199 sys_write(1, h, 2)
200 return 0
201}
202// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
203// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
204// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
205// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
206func fl_fdn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
207// one labelled equality check: PASS iff got==exp.
208func fl_check(name: *u8, got: i64, exp: i64) -> i64 {
209 if got == exp {
210 fl_puts(" PASS " as *u8); fl_puts(name)
211 fl_puts(" (== " as *u8); fl_putn(exp); fl_puts(")\n" as *u8)
212 return 1
213 }
214 fl_puts(" FAIL " as *u8); fl_puts(name)
215 fl_puts(" got=" as *u8); fl_putn(got)
216 fl_puts(" exp=" as *u8); fl_putn(exp); fl_puts("\n" as *u8)
217 return 0
218}
219// byte-exact buffer compare (the h2_check_bytes pattern).
220func fl_check_bytes(name: *u8, got: *u8, glen: i64, exp: *u8, elen: i64) -> i64 {
221 if glen != elen {
222 fl_puts(" FAIL " as *u8); fl_puts(name)
223 fl_puts(" length got=" as *u8); fl_putn(glen)
224 fl_puts(" exp=" as *u8); fl_putn(elen); fl_puts("\n" as *u8)
225 return 0
226 }
227 var i: i64 = 0
228 while i < glen {
229 if (got[i] & 0xff) != (exp[i] & 0xff) {
230 fl_puts(" FAIL " as *u8); fl_puts(name)
231 fl_puts(" byte[" as *u8); fl_putn(i)
232 fl_puts("] got=" as *u8); fl_puthex2(got[i] & 0xff)
233 fl_puts(" exp=" as *u8); fl_puthex2(exp[i] & 0xff); fl_puts("\n" as *u8)
234 return 0
235 }
236 i = i + 1
237 }
238 fl_puts(" PASS " as *u8); fl_puts(name)
239 fl_puts(" (" as *u8); fl_putn(glen); fl_puts(" bytes)\n" as *u8)
240 return 1
241}
242
243func main() -> i64 {
244 var pass: i64 = 0
245 var tot: i64 = 0
246 fl_puts("nx_h2_flow gate (RFC 9113 5.2/6.9 flow control, FOUNDED on nx_h2_frame)\n" as *u8)
247
248 let out: *u8 = sys_mmap(256)
249 let exp: *u8 = sys_mmap(256)
250
251 // =================================================================
252 // §6.9 byte-exact WINDOW_UPDATE / SETTINGS KATs -- via the COMPOSED
253 // nx_h2_frame builders (single source of truth with the codec).
254 // =================================================================
255
256 // ---- WINDOW_UPDATE +65535 on connection (stream 0), 13 bytes ----
257 // 00 00 04 08 00 00 00 00 00 00 00 ff ff
258 var o: i64 = h2_flow_write_window_update(out, 0, 0, K_MAGIC_65535)
259 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x04 as u8; exp[3]=0x08 as u8
260 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
261 exp[8]=0x00 as u8; exp[9]=0x00 as u8; exp[10]=0x00 as u8; exp[11]=0xff as u8
262 exp[12]=0xff as u8
263 pass = pass + fl_check_bytes("WINDOW_UPDATE +65535 conn (stream 0)" as *u8, out, o, exp, 13); tot = tot + 1
264
265 // ---- WINDOW_UPDATE +65535 on stream 1, 13 bytes ----
266 // 00 00 04 08 00 00 00 00 01 00 00 ff ff
267 o = h2_flow_write_window_update(out, 0, 1, K_MAGIC_65535)
268 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x04 as u8; exp[3]=0x08 as u8
269 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
270 exp[8]=0x01 as u8; exp[9]=0x00 as u8; exp[10]=0x00 as u8; exp[11]=0xff as u8
271 exp[12]=0xff as u8
272 pass = pass + fl_check_bytes("WINDOW_UPDATE +65535 stream 1" as *u8, out, o, exp, 13); tot = tot + 1
273
274 // ---- WINDOW_UPDATE +2147483647 (max, 0x7FFFFFFF) on stream 1, 13 bytes ----
275 // 00 00 04 08 00 00 00 00 01 7f ff ff ff
276 o = h2_flow_write_window_update(out, 0, 1, K_MAGIC_2147483647)
277 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x04 as u8; exp[3]=0x08 as u8
278 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
279 exp[8]=0x01 as u8; exp[9]=0x7f as u8; exp[10]=0xff as u8; exp[11]=0xff as u8
280 exp[12]=0xff as u8
281 pass = pass + fl_check_bytes("WINDOW_UPDATE +2147483647 (max) stream 1" as *u8, out, o, exp, 13); tot = tot + 1
282
283 // ---- SETTINGS INITIAL_WINDOW_SIZE(0x04)=65535 (15 bytes) ----
284 // 00 00 06 04 00 00 00 00 00 00 04 00 00 ff ff (composed from nx_h2_frame)
285 o = h2_frame_write_settings_param(out, 0, 0x04, K_MAGIC_65535)
286 exp[0]=0x00 as u8; exp[1]=0x00 as u8; exp[2]=0x06 as u8; exp[3]=0x04 as u8
287 exp[4]=0x00 as u8; exp[5]=0x00 as u8; exp[6]=0x00 as u8; exp[7]=0x00 as u8
288 exp[8]=0x00 as u8; exp[9]=0x00 as u8; exp[10]=0x04 as u8; exp[11]=0x00 as u8
289 exp[12]=0x00 as u8; exp[13]=0xff as u8; exp[14]=0xff as u8
290 pass = pass + fl_check_bytes("SETTINGS INITIAL_WINDOW_SIZE=65535" as *u8, out, o, exp, 15); tot = tot + 1
291
292 // =================================================================
293 // WINDOW ARITHMETIC across DATA + WINDOW_UPDATE -- the headline KAT.
294 // Two independent windows (connection + stream); a DATA frame debits BOTH.
295 // =================================================================
296
297 // ---- h2_flow_init -> 65535 (default SETTINGS_INITIAL_WINDOW_SIZE) ----
298 let connw: *i64 = sys_mmap(16) as *i64
299 let strmw: *i64 = sys_mmap(16) as *i64
300 pass = pass + fl_check("init(conn) == 65535" as *u8, h2_flow_init(connw), K_MAGIC_65535); tot = tot + 1
301 pass = pass + fl_check("init(stream) == 65535" as *u8, h2_flow_init(strmw), K_MAGIC_65535); tot = tot + 1
302
303 // ---- consume(window=65535, 100) -> 65435 (DATA debits the window) ----
304 pass = pass + fl_check("consume(65535, 100) -> 65435" as *u8, h2_flow_consume(strmw, 100), K_MAGIC_65435); tot = tot + 1
305 pass = pass + fl_check(" stream window now 65435" as *u8, strmw[0], K_MAGIC_65435); tot = tot + 1
306
307 // ---- a DATA frame debits BOTH windows: connection also drops by 100 ----
308 pass = pass + fl_check("consume(conn 65535, 100) -> 65435" as *u8, h2_flow_consume(connw, 100), K_MAGIC_65435); tot = tot + 1
309
310 // ---- WINDOW_UPDATE +65535 replenishes the stream window: 65435 -> 130970 ----
311 pass = pass + fl_check("apply_window_update(stream 65435, +65535) -> 130970" as *u8, h2_flow_apply_window_update(strmw, K_MAGIC_65535), K_MAGIC_130970); tot = tot + 1
312
313 // ---- spec micro-KAT: apply_window_update(0, 65535) -> 65535 ----
314 let zerow: *i64 = sys_mmap(16) as *i64
315 zerow[0] = 0
316 pass = pass + fl_check("apply_window_update(0, +65535) -> 65535" as *u8, h2_flow_apply_window_update(zerow, K_MAGIC_65535), K_MAGIC_65535); tot = tot + 1
317
318 // ---- flow control applies ONLY to DATA (§5.2.1) ----
319 pass = pass + fl_check("is_data_frame(DATA 0x00) == 1" as *u8, h2_flow_is_data_frame(0x00), 1); tot = tot + 1
320 pass = pass + fl_check("is_data_frame(HEADERS 0x01) == 0" as *u8, h2_flow_is_data_frame(0x01), 0); tot = tot + 1
321 pass = pass + fl_check("is_data_frame(WINDOW_UPDATE 0x08) == 0" as *u8, h2_flow_is_data_frame(0x08), 0); tot = tot + 1
322 pass = pass + fl_check("is_data_frame(SETTINGS 0x04) == 0" as *u8, h2_flow_is_data_frame(0x04), 0); tot = tot + 1
323
324 // ---- WINDOW_UPDATE-length validity ----
325 pass = pass + fl_check("window_update_length_valid(4) == 1" as *u8, h2_window_update_length_valid(4), 1); tot = tot + 1
326
327 // ---- §6.9.2 SETTINGS_INITIAL_WINDOW_SIZE change adjusts STREAM windows by
328 // delta = (new - old). The RFC formula is `window += (new - old)`:
329 // starting at the default 65535 with old=65535, new=0 the delta is
330 // -65535 and the window lands at 65535 + (-65535) = 0 (the new initial,
331 // since no DATA has flowed yet). This is the self-consistent
332 // known-answer (the spec's "-65535" example was the DELTA, not the
333 // resulting window for an untouched 65535 window). ----
334 let adjw: *i64 = sys_mmap(16) as *i64
335 h2_flow_init(adjw) // K_MAGIC_65535
336 pass = pass + fl_check("apply_settings_initial(window=65535, old=65535, new=0) -> 0 (delta -65535)" as *u8, h2_flow_apply_settings_initial(adjw, K_MAGIC_65535, 0), 0); tot = tot + 1
337
338 // ---- §6.9.2 the NEGATIVE-window case (the legal-negative known-answer):
339 // a stream that has already consumed DATA (window debited to 100) then
340 // receives a SETTINGS_INITIAL_WINDOW_SIZE shrink old=65535 -> new=0:
341 // delta -65535 drives 100 -> 100 + (-65535) = -65435, which is LEGAL
342 // (the stream MUST NOT send DATA until a WINDOW_UPDATE lifts it positive,
343 // §6.9.2). ----
344 let negw: *i64 = sys_mmap(16) as *i64
345 negw[0] = 100 // already-consumed stream window
346 pass = pass + fl_check("apply_settings_initial(window=100, old=65535, new=0) -> -65435 (negative LEGAL)" as *u8, h2_flow_apply_settings_initial(negw, K_MAGIC_65535, 0), 0 - K_MAGIC_65435); tot = tot + 1
347 pass = pass + fl_check(" stream window now -65435 (legal per 6.9.2)" as *u8, negw[0], 0 - K_MAGIC_65435); tot = tot + 1
348 // a follow-up WINDOW_UPDATE brings the negative window back positive:
349 // -65435 + 100000 = 34565.
350 pass = pass + fl_check("recover negative window: apply_window_update(-65435, +100000) -> 34565" as *u8, h2_flow_apply_window_update(negw, K_MAGIC_100000), K_MAGIC_34565); tot = tot + 1
351
352 // =================================================================
353 // TAMPER / ILLEGAL CASES -- each on a SEPARATE scratch window cell; the
354 // canonical KAT windows (connw, strmw, zerow, adjw) are NEVER entangled with
355 // the rejection path (defect-(c) discipline). A rejection is a strictly-
356 // negative return carrying the RFC 9113 §7 error code.
357 // =================================================================
358
359 // ---- TAMPER 1: WINDOW_UPDATE increment 0 -> -PROTOCOL_ERROR (0x01) ----
360 // wire: 00 00 04 08 00 00 00 00 01 00 00 00 00
361 let tw1: *i64 = sys_mmap(16) as *i64
362 h2_flow_init(tw1) // canonical K_MAGIC_65535
363 let zinc_rc: i64 = h2_flow_apply_window_update(tw1, 0)
364 var t1ok: i64 = 0
365 if zinc_rc == 0 - h2_flow_err_protocol() { if tw1[0] == K_MAGIC_65535 { t1ok = 1 } } // rejected AND window untouched
366 if t1ok == 1 { fl_puts(" PASS tamper WINDOW_UPDATE increment 0 -> -PROTOCOL_ERROR (rc=" as *u8); fl_putn(zinc_rc); fl_puts(", window unchanged=" as *u8); fl_putn(tw1[0]); fl_puts(")\n" as *u8); pass = pass + 1 }
367 if t1ok == 0 { fl_puts(" FAIL tamper WINDOW_UPDATE increment 0 (rc=" as *u8); fl_putn(zinc_rc); fl_puts(" window=" as *u8); fl_putn(tw1[0]); fl_puts(")\n" as *u8) }
368 tot = tot + 1
369
370 // ---- TAMPER 2: increment overflowing 2^31-1 -> -FLOW_CONTROL_ERROR (0x03) ----
371 // seed the window at the ceiling 0x7FFFFFFF, then +1 must be rejected.
372 let tw2: *i64 = sys_mmap(16) as *i64
373 tw2[0] = h2_flow_max_window() // 0x7FFFFFFF = K_MAGIC_2147483647
374 let ovf_rc: i64 = h2_flow_apply_window_update(tw2, 1)
375 var t2ok: i64 = 0
376 if ovf_rc == 0 - h2_flow_err_flow_control() { if tw2[0] == h2_flow_max_window() { t2ok = 1 } } // rejected AND window untouched
377 if t2ok == 1 { fl_puts(" PASS tamper WINDOW_UPDATE overflow 2^31-1 + 1 -> -FLOW_CONTROL_ERROR (rc=" as *u8); fl_putn(ovf_rc); fl_puts(", window unchanged=" as *u8); fl_putn(tw2[0]); fl_puts(")\n" as *u8); pass = pass + 1 }
378 if t2ok == 0 { fl_puts(" FAIL tamper WINDOW_UPDATE overflow (rc=" as *u8); fl_putn(ovf_rc); fl_puts(" window=" as *u8); fl_putn(tw2[0]); fl_puts(")\n" as *u8) }
379 tot = tot + 1
380
381 // ---- TAMPER 3: consume(window=50, data_len=100) overdraw -> -FLOW_CONTROL_ERROR ----
382 let tw3: *i64 = sys_mmap(16) as *i64
383 tw3[0] = 50
384 let over_rc: i64 = h2_flow_consume(tw3, 100)
385 var t3ok: i64 = 0
386 if over_rc == 0 - h2_flow_err_flow_control() { if tw3[0] == 50 { t3ok = 1 } } // rejected AND window not debited
387 if t3ok == 1 { fl_puts(" PASS tamper consume(50, 100) overdraw -> -FLOW_CONTROL_ERROR (rc=" as *u8); fl_putn(over_rc); fl_puts(", window unchanged=" as *u8); fl_putn(tw3[0]); fl_puts(")\n" as *u8); pass = pass + 1 }
388 if t3ok == 0 { fl_puts(" FAIL tamper consume overdraw (rc=" as *u8); fl_putn(over_rc); fl_puts(" window=" as *u8); fl_putn(tw3[0]); fl_puts(")\n" as *u8) }
389 tot = tot + 1
390
391 // ---- TAMPER 4: window_update_length_valid(5) -> 0 (FRAME_SIZE_ERROR class) ----
392 var t4ok: i64 = 0
393 if h2_window_update_length_valid(5) == 0 { if h2_window_update_length_valid(4) == 1 { t4ok = 1 } }
394 if t4ok == 1 { fl_puts(" PASS tamper WINDOW_UPDATE length 5 rejected (FRAME_SIZE_ERROR class)\n" as *u8); pass = pass + 1 }
395 if t4ok == 0 { fl_puts(" FAIL tamper WINDOW_UPDATE length 5 NOT rejected\n" as *u8) }
396 tot = tot + 1
397
398 // ---- sanity: the canonical KAT windows were NOT corrupted by the tamper block ----
399 pass = pass + fl_check("KAT-untouched: recovered stream window still 34565" as *u8, negw[0], K_MAGIC_34565); tot = tot + 1
400
401 fl_puts("---- h2_flow gate: passed " as *u8); fl_putn(pass); fl_puts(" / " as *u8); fl_putn(tot); fl_puts("\n" as *u8)
402 if pass == tot {
403 // DURABLE EVIDENCE (mirror nx_h2_frame.nx:586 / nx_h2_stream.nx:362):
404 // stdout evaporates and cannot anchor a row_markers entry -- append ONE
405 // marker/reconcile-recognised status line. Reached ONLY when every KAT
406 // + all four tamper cases pass (pass == tot).
407 let lfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_flow.log" as *u8, 0x1a4)
408 if lfd >= 0 {
409 sys_write(lfd, "R4-H2-005-GATE organ=nx_h2_flow kats=" as *u8, 37)
410 fl_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); fl_fdn(lfd, tot)
411 sys_write(lfd, " tamper=ok verdict=GREEN\n" as *u8, 25)
412 sys_close(lfd)
413 }
414 sys_exit(0)
415 }
416 // RED path: still write a durable line so the failure is anchored, then exit 1.
417 let rfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_flow.log" as *u8, 0x1a4)
418 if rfd >= 0 {
419 sys_write(rfd, "R4-H2-005-GATE organ=nx_h2_flow kats=" as *u8, 37)
420 fl_fdn(rfd, pass); sys_write(rfd, "/" as *u8, 1); fl_fdn(rfd, tot)
421 sys_write(rfd, " tamper=?? verdict=RED\n" as *u8, 23)
422 sys_close(rfd)
423 }
424 sys_exit(1)
425 return 0
426}