code wiki / (root) / nx_h2_flow.nx

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}