code wiki / _hdl_build / nx_deadline_gate.nx

nx_deadline_gate.nx source

↩ module page · 238 lines · 14152 B

1// nx_deadline_gate.nx -- the referee for request-deadline propagation (nx_deadline_lib). 2// 3// The property that matters is not "does it parse a number". It is that the clamp can only ever SHORTEN a 4// wait the caller already chose, because that is what makes this a COMPOSITION of the estate's existing 5// per-lane timeout rulers instead of a second, rival one. So the load-bearing tooth is exhaustive over a 6// grid: for every (deadline, now, want) tried, dl_wait_ms <= want AND >= 0. A primitive that could return 7// more than `want` would silently extend somebody's timeout the first time it was adopted. 8// 9// It also asserts the PRODUCER and CONSUMER agree on the header name by reading the producer's SOURCE -- 10// nx_sites_daemon_v2 carries its own NX_SD2_DEADLINE_HDR copy today, and a comment asking people to keep 11// two literals in step is not a control. This tooth turns that into a RED. 12// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). expect_exit: 0 13import "nx_syscalls.nx" 14import "nx_gate_verdict.nx" 15import "nx_deadline_lib.nx" 16 17const DG_SRC_A: *u8 = "buildroot/runtime/_hdl_build/nx_sites_daemon_v2.nx" 18const DG_SRC_B: *u8 = "runtime/_hdl_build/nx_sites_daemon_v2.nx" 19const DG_SRC_C: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/runtime/_hdl_build/nx_sites_daemon_v2.nx" 20const DG_PROD_CONST: *u8 = "const NX_SD2_DEADLINE_HDR: *u8 = \"" 21// Fixture request buffer. One size for every fixture below, comfortably above the largest of them, so a 22// new fixture cannot silently outgrow a per-site number nobody re-checked. 23const DG_FX_BUF: i64 = 2048 24// The deadline the header-carrying fixture DECLARES. It is a MATCHED PAIR with the digits written inside 25// that fixture's header text, and the pairing is self-checking rather than trusted: if the two ever 26// disagree, the scan tooth that compares them fails. That is the only honest way to bind a const to a 27// string literal, since the literal cannot be derived from the const. 28const DG_FX_DEADLINE_MS: i64 = 1234567 29// Offset of the deadline header line inside that same fixture ("POST /api/x HTTP/1.1" 20 + CRLF 2 + 30// "Host: a" 7 + CRLF 2 = 31) and a window that ends MID-NAME. Asserted, not trusted: the companion tooth 31// proves the FULL name matches at this offset, so a 0 from the short window is a TRUNCATION refusal and 32// not a name mismatch. Getting this wrong is how a tooth ends up testing a different thing than it says. 33const DG_FX_HDR_OFF: i64 = 31 34const DG_FX_TRUNC_LEN: i64 = 6 35 36func dg_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 37 38func dg_find(hay: *u8, hn: i64, needle: *u8) -> i64 { 39 let m: i64 = dg_len(needle) 40 if m == 0 { return 0 - 1 } 41 if m > hn { return 0 - 1 } 42 var i: i64 = 0 43 let last: i64 = hn - m 44 while i <= last { 45 var j: i64 = 0 46 var ok: i64 = 1 47 while j < m { 48 if hay[i + j] != needle[j] { ok = 0; j = m } else { j = j + 1 } 49 } 50 if ok == 1 { return i } 51 i = i + 1 52 } 53 return 0 - 1 54} 55 56// Fixture writer. Requests are assembled AT RUNTIME rather than stored as one blob so each tooth can 57// state its own shape inline and no fixture can silently drift out from under the tooth that names it. 58func dg_put(dst: *u8, off: i64, s: *u8) -> i64 { 59 var i: i64 = 0 60 while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } 61 return off + i 62} 63func dg_read_any(box: *i64) -> *u8 { 64 var b: *u8 = sys_read_file(DG_SRC_A, box) 65 if (b as i64) == 0 { b = sys_read_file(DG_SRC_B, box) } 66 if (b as i64) == 0 { b = sys_read_file(DG_SRC_C, box) } 67 return b 68} 69 70func main(argc: i64, argv: *i64) -> i64 { 71 gv_head("nx_deadline_gate -- request deadline propagation: the clamp may only ever SHORTEN, and the producer and consumer must agree on the header name" as *u8) 72 let ctr: *i64 = gv_ctr() 73 74 // ---- parsing: ABSENT and ZERO are different answers ---- 75 gv_check_eq("parse: a plain decimal value parses to that deadline" as *u8, 76 dl_parse_ms("1788469400123" as *u8, 13), 1788469400123, ctr) 77 gv_check_eq("parse: one leading space (as a header value may carry) is tolerated" as *u8, 78 dl_parse_ms(" 4242" as *u8, 5), 4242, ctr) 79 gv_check_eq("parse: it stops at the first non-digit, so a trailing CR or parameter is harmless" as *u8, 80 dl_parse_ms("500\r" as *u8, 4), 500, ctr) 81 gv_check_eq("parse: a literal 0 is a REAL (long-expired) deadline, NOT absent" as *u8, 82 dl_parse_ms("0" as *u8, 1), 0, ctr) 83 gv_check_eq("neg-control-an-EMPTY-value-is-ABSENT-not-zero (a missing header must never impose a zero budget)" as *u8, 84 dl_parse_ms("" as *u8, 0), DL_ABSENT, ctr) 85 gv_check_eq("neg-control-a-NON-NUMERIC-value-is-ABSENT-not-a-partial-number" as *u8, 86 dl_parse_ms("abc" as *u8, 3), DL_ABSENT, ctr) 87 gv_check_eq("neg-control-an-OVERFLOWING-value-REFUSES-rather-than-wrapping (a wrapped deadline is a wrong answer with a plausible shape)" as *u8, 88 dl_parse_ms("99999999999999999999" as *u8, 20), DL_ABSENT, ctr) 89 90 // ---- the three named states ---- 91 gv_check_eq("state: no declared deadline is NO-DEADLINE-DECLARED, never EXPIRED" as *u8, 92 dl_state(DL_ABSENT, 1000), DL_S_NONE, ctr) 93 gv_check_eq("state: a deadline in the future is LIVE" as *u8, 94 dl_state(2000, 1000), DL_S_LIVE, ctr) 95 gv_check_eq("state: a deadline already reached is EXPIRED (<= now, so equality counts as gone)" as *u8, 96 dl_state(1000, 1000), DL_S_EXPIRED, ctr) 97 gv_check_eq("remaining: a live deadline reports the milliseconds left" as *u8, 98 dl_remaining_ms(2500, 1000), 1500, ctr) 99 gv_check_eq("remaining: absent stays ABSENT rather than becoming a number" as *u8, 100 dl_remaining_ms(DL_ABSENT, 1000), DL_ABSENT, ctr) 101 102 // ---- THE CLAMP: the property the whole lib exists for ---- 103 gv_check_eq("clamp: with NO deadline the caller's own wait is returned UNCHANGED (never invent a budget)" as *u8, 104 dl_wait_ms(DL_ABSENT, 1000, 5000), 5000, ctr) 105 gv_check_eq("clamp: an EXPIRED deadline yields 0 -- do not start work that cannot be delivered" as *u8, 106 dl_wait_ms(900, 1000, 5000), 0, ctr) 107 gv_check_eq("clamp: when less time remains than the caller wanted, the REMAINING time wins (honouring)" as *u8, 108 dl_wait_ms(1200, 1000, 5000), 200, ctr) 109 gv_check_eq("clamp: when MORE time remains than the caller wanted, the caller's want wins (composing, not extending)" as *u8, 110 dl_wait_ms(9000, 1000, 5000), 5000, ctr) 111 gv_check_eq("neg-control-a-negative-want-can-never-produce-a-negative-wait" as *u8, 112 dl_wait_ms(9000, 1000, 0 - 7), 0, ctr) 113 114 // EXHAUSTIVE over a grid: the two invariants that make this a composition rather than a rival ruler. 115 // ★A PROPERTY CHECKED ON THREE HAND-PICKED CASES IS AN ANECDOTE; CHECKED OVER EVERY COMBINATION IT IS 116 // AN INVARIANT -- and this is the one that decides whether adopting the lib can hurt a caller. 117 var never_longer: i64 = 1 118 var never_negative: i64 = 1 119 var tried: i64 = 0 120 var saw_shorten: i64 = 0 121 var d: i64 = 0 122 while d < 12 { 123 var w: i64 = 0 124 while w < 12 { 125 let now: i64 = 500 126 let dl: i64 = 400 + d * 40 127 let want: i64 = w * 40 128 let got: i64 = dl_wait_ms(dl, now, want) 129 if got > want { never_longer = 0 } 130 if got < 0 { never_negative = 0 } 131 if got < want { saw_shorten = 1 } 132 tried = tried + 1 133 w = w + 1 134 } 135 d = d + 1 136 } 137 gv_check("CLAMP INVARIANT (exhaustive, 144 combinations): dl_wait_ms NEVER returns more than the caller's own want -- it composes their timeout, it cannot extend it" as *u8, 138 never_longer, ctr) 139 gv_check("CLAMP INVARIANT (exhaustive): dl_wait_ms never returns a negative wait" as *u8, 140 never_negative, ctr) 141 gv_check("fixture-reached-condition: the grid actually exercised the SHORTENING branch (an all-pass-through grid would prove nothing)" as *u8, 142 saw_shorten, ctr) 143 gv_check_eq("fixture-reached-condition: the grid visited every combination it claims" as *u8, 144 tried, 144, ctr) 145 146 // ---- PRODUCER/CONSUMER AGREEMENT, read from the producer's own source ---- 147 let box: *i64 = sys_mmap(16) as *i64 148 let src: *u8 = dg_read_any(box) 149 var sn: i64 = 0 150 if (src as i64) != 0 { sn = box[0] } 151 gv_need("the front-door source is readable from one of the three known roots (else this axis abstains rather than acquitting)" as *u8, 152 ((sn > 0) as i64), ctr) 153 if sn > 0 { 154 let at: i64 = dg_find(src, sn, DG_PROD_CONST) 155 gv_check("producer: the front door declares NX_SD2_DEADLINE_HDR" as *u8, 156 (at >= 0) as i64, ctr) 157 if at >= 0 { 158 let vp: *u8 = ((src as i64) + at + dg_len(DG_PROD_CONST)) as *u8 159 let want: i64 = dg_len(DL_HDR) 160 var same: i64 = 1 161 var k: i64 = 0 162 while k < want { 163 if vp[k] != DL_HDR[k] { same = 0; k = want } else { k = k + 1 } 164 } 165 gv_check("SSOT: the producer's header literal is BYTE-EQUAL to this lib's DL_HDR -- two copies exist, so a divergence is a RED rather than a comment asking people to be careful" as *u8, 166 same, ctr) 167 } 168 } 169 170 // ---- FINDING THE DEADLINE (dl_keylen / dl_is_hdr_line / dl_scan_request). 171 // These are in the LIB and therefore provable IN-PROCESS. Had they been written as sd2_-prefixed 172 // copies inside the front door, they would have sat on the far side of a fork boundary from this gate 173 // and could only ever have been tested end-to-end -- which in practice means not tested at all. 174 let kl: i64 = dl_keylen() 175 gv_check_eq("keylen: DERIVED from DL_HDR and lands exactly one past the colon, so it can never drift from the literal" as *u8, 176 (DL_HDR[kl-1] as i64), 58, ctr) 177 gv_check_eq("keylen: it stops AT the colon rather than running to the end of the literal (the trailing space is a value, not a name)" as *u8, 178 kl, dg_len(DL_HDR) - 1, ctr) 179 180 let fx: *u8 = sys_mmap(DG_FX_BUF) 181 var fn: i64 = 0 182 fn = dg_put(fx, 0, "POST /api/x HTTP/1.1\r\nHost: a\r\nX-Nishi-Deadline-Mono-Ms: 1234567\r\nContent-Length: 5\r\n\r\nhello" as *u8) 183 gv_check_eq("scan: a deadline carried in the request headers is found and parsed" as *u8, 184 dl_scan_request(fx, fn), DG_FX_DEADLINE_MS, ctr) 185 186 let fx2: *u8 = sys_mmap(DG_FX_BUF) 187 var fn2: i64 = 0 188 fn2 = dg_put(fx2, 0, "GET /x HTTP/1.1\r\nHost: a\r\nAccept: */*\r\n\r\n" as *u8) 189 gv_check_eq("neg-control-a-request-declaring-NO-deadline-scans-to-ABSENT-not-to-a-number" as *u8, 190 dl_scan_request(fx2, fn2), DL_ABSENT, ctr) 191 192 let fx3: *u8 = sys_mmap(DG_FX_BUF) 193 var fn3: i64 = 0 194 fn3 = dg_put(fx3, 0, "GET /x HTTP/1.1\r\nx-NISHI-deadline-MONO-ms: 777\r\n\r\n" as *u8) 195 gv_check_eq("scan: the header is matched case-INSENSITIVELY (RFC 9110), so a hop that re-cased it is still honoured" as *u8, 196 dl_scan_request(fx3, fn3), 777, ctr) 197 198 // THE ONE THAT MATTERS ON A PUBLIC FRONT DOOR. The body is attacker-controlled. A request that could 199 // SHORTEN its own deadline is merely odd; one that could LENGTHEN it walks straight past the budget 200 // this whole file exists to enforce. The scan must stop at the blank line. 201 let fx4: *u8 = sys_mmap(DG_FX_BUF) 202 var fn4: i64 = 0 203 fn4 = dg_put(fx4, 0, "POST /x HTTP/1.1\r\nHost: a\r\nContent-Length: 38\r\n\r\nX-Nishi-Deadline-Mono-Ms: 99999999999\r\n" as *u8) 204 gv_check_eq("neg-control-a-deadline-shaped-string-in-the-POST-BODY-is-NOT-readable-as-a-header (a body that could lengthen its own deadline is a bypass)" as *u8, 205 dl_scan_request(fx4, fn4), DL_ABSENT, ctr) 206 gv_check("fixture-reached-condition: that body fixture really does contain the header text it must not honour, so the tooth above is not passing on an empty search" as *u8, 207 dg_find(fx4, fn4, "X-Nishi-Deadline-Mono-Ms: 99999999999" as *u8) >= 0, ctr) 208 209 let fx5: *u8 = sys_mmap(DG_FX_BUF) 210 var fn5: i64 = 0 211 fn5 = dg_put(fx5, 0, "GET /x HTTP/1.1\r\nX-Nishi-Deadline-Mono-Ms: 111\r\nX-Nishi-Deadline-Mono-Ms: 222\r\n\r\n" as *u8) 212 gv_check_eq("scan: with the header DUPLICATED the FIRST wins -- the same rule every first-match reader downstream follows, so the edge and the backend resolve it identically" as *u8, 213 dl_scan_request(fx5, fn5), 111, ctr) 214 215 let fx6: *u8 = sys_mmap(DG_FX_BUF) 216 var fn6: i64 = 0 217 fn6 = dg_put(fx6, 0, "X-Nishi-Deadline-Mono-Zz: 5\r\n\r\n" as *u8) 218 gv_check_eq("neg-control-a-DIFFERENT-header-sharing-a-long-prefix-does-not-match" as *u8, 219 dl_is_hdr_line(fx6, 0, fn6), 0, ctr) 220 // The PAIR is the point. Tooth one proves the full header name really is at DG_FX_HDR_OFF, so tooth 221 // two's 0 can only come from the buffer ending mid-name. Without the first, the second would pass just 222 // as happily against an offset holding some unrelated header -- a tooth testing something other than 223 // the bound it is named for. A NEG-CONTROL THAT COULD RETURN 0 FOR A SECOND REASON PROVES NEITHER. 224 gv_check_eq("fixture-reached-condition: the full deadline header name IS present at the offset the truncation tooth uses" as *u8, 225 dl_is_hdr_line(fx, DG_FX_HDR_OFF, DG_FX_HDR_OFF + dl_keylen()), 1, ctr) 226 gv_check_eq("neg-control-a-buffer-TRUNCATED-mid-name-returns-0-rather-than-reading-past-n" as *u8, 227 dl_is_hdr_line(fx, DG_FX_HDR_OFF, DG_FX_HDR_OFF + DG_FX_TRUNC_LEN), 0, ctr) 228 229 gv_kv("scan_headers_ms" as *u8, dl_scan_request(fx, fn)) 230 gv_kv("scan_body_absent" as *u8, dl_scan_request(fx4, fn4)) 231 gv_kv("dl_keylen" as *u8, kl) 232 gv_kv("grid_combinations" as *u8, tried) 233 gv_kv("producer_src_bytes" as *u8, sn) 234 gv_kv("hdr_name_len" as *u8, dg_len(DL_HDR)) 235 236 return gv_verdict("deadline" as *u8, ctr, 237 "the deadline clamp composes a caller's timeout and provably cannot extend it (exhaustive over 144 combinations), ABSENT is never confused with a zero budget, an overflowing value refuses rather than wraps, and the producer's header literal is asserted byte-equal to the consumer's" as *u8) 238}