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}