nx_deadline_lib.nx source
↩ module page · 173 lines · 9164 B
1// nx_deadline_lib.nx -- REQUEST DEADLINE PROPAGATION: the CONSUMER half of X-Nishi-Deadline-Mono-Ms.
2//
3// WHY IT EXISTS, and it is a defect I created myself on 2026-09-03. The front door now stamps
4// `X-Nishi-Deadline-Mono-Ms: <monotonic ms>` on every buffered proxy hop -- and NOTHING READ IT. That is
5// the estate's own law firing on its author: ★AN ENVELOPE MEASURED, PRINTED, AND THEN DROPPED AT THE NEXT
6// HOP IS AN ENVELOPE THAT WAS NEVER PUBLISHED. A producer with no consumer is not half a feature, it is
7// zero of one, and it reads as progress on every board.
8//
9// WHY IT DOES NOT PARSE HEADERS. `gw_hdr_val` is byte-identical in FIVE gateway sources
10// (sha 9410a9c6...), so writing a sixth copy here would add a duplicate ruler to fix a duplicate ruler.
11// This lib takes the VALUE and answers the QUESTION; each backend keeps whatever header reader it already
12// has. ★SEPARATE THE PARSER FROM THE POLICY AND THE POLICY BECOMES ADOPTABLE BY EVERY BACKEND AT ONCE.
13//
14// WHY THE CLAMP IS THE WHOLE POINT -- IT COMPOSES, IT DOES NOT REPLACE. The estate already has a timeout
15// ruler per lane (`pr_lookup_tmo` and friends), and the standing instruction for this rung is to COMPOSE
16// it rather than add a second. So `dl_wait_ms` NEVER returns more than the caller's own `want`: it can
17// only ever SHORTEN a wait the caller already chose. That direction is load-bearing --
18// ★A DEADLINE PRIMITIVE THAT CAN EXTEND A CALLER'S TIMEOUT IS A SECOND TIMEOUT RULER WEARING A
19// DEADLINE'S CLOTHES, AND THE TWO WILL DISAGREE THE FIRST TIME EITHER IS TUNED.
20// With that property, "honouring the deadline" is BY CONSTRUCTION for any caller that routes its wait
21// through this function, and adoption is one call rather than a policy everyone must remember.
22//
23// THE CLOCK IS MONOTONIC AND PER-BOOT, WHICH IS WHY THE HEADER SAYS SO IN ITS NAME. sys_now_ms is
24// CLOCK_MONOTONIC: comparable only within one boot of one host. That is exactly right for an edge and a
25// backend on the SAME box and exactly wrong across machines, so the name carries the constraint where a
26// reader cannot miss it -- a cross-host caller must not use this without a wall-clock variant.
27// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). lib (no main)
28import "nx_syscalls.nx"
29
30// THE HEADER NAME LIVES HERE, ONCE. The producer (nx_sites_daemon_v2's NX_SD2_DEADLINE_HDR) carries its
31// own copy today; nx_deadline_gate asserts the two are EQUAL by reading the producer's source, so a
32// divergence cannot ship silently even while two copies exist. ★WHEN TWO ORGANS MUST AGREE AND CANNOT YET
33// SHARE A CONSTANT, MAKE THE DISAGREEMENT A RED GATE RATHER THAN A COMMENT ASKING PEOPLE TO BE CAREFUL.
34const DL_HDR: *u8 = "X-Nishi-Deadline-Mono-Ms: "
35
36const DL_ABSENT: i64 = 0 - 1 // no parseable deadline in the value
37
38// states, named so a caller can never confuse "no deadline" with "deadline already gone"
39const DL_S_NONE: i64 = 0 // the request declared no deadline -- do NOT invent one
40const DL_S_LIVE: i64 = 1 // a deadline exists and has time left
41const DL_S_EXPIRED: i64 = 2 // a deadline exists and has passed
42
43func dl_state_name(s: i64) -> *u8 {
44 if s == DL_S_NONE { return "NO-DEADLINE-DECLARED" as *u8 }
45 if s == DL_S_LIVE { return "LIVE" as *u8 }
46 return "EXPIRED" as *u8
47}
48
49// Parse a decimal monotonic-ms deadline out of a header VALUE. Returns DL_ABSENT for an empty value, for
50// one with no leading digits, and for one that would overflow -- never a partial number silently treated
51// as a deadline. Stops at the first non-digit, so a trailing "\r" or " ; q=1" is harmless.
52// ⚠ABSENT AND ZERO ARE DIFFERENT ANSWERS: a literal "0" is a real (long-expired) deadline, while a
53// missing header is a caller who never asked for one, and conflating them would silently impose a
54// zero-budget deadline on every request that lacks the header.
55func dl_parse_ms(val: *u8, n: i64) -> i64 {
56 if n <= 0 { return DL_ABSENT }
57 var i: i64 = 0
58 // skip one optional leading space, as a header value may carry it
59 if val[0] == (32 as u8) { i = 1 }
60 var seen: i64 = 0
61 var v: i64 = 0
62 var go: i64 = 1
63 while go == 1 {
64 if i >= n { go = 0 } else {
65 let c: i64 = val[i] as i64
66 if c < 48 { go = 0 } else {
67 if c > 57 { go = 0 } else {
68 // refuse rather than wrap: a wrapped deadline is a wrong answer with a plausible shape
69 if v > 900000000000000 { return DL_ABSENT }
70 v = v * 10 + (c - 48)
71 seen = 1
72 i = i + 1
73 }
74 }
75 }
76 }
77 if seen == 0 { return DL_ABSENT }
78 return v
79}
80
81// ---- FINDING THE DEADLINE. This lives HERE, not in a daemon, because every backend that honours a
82// deadline must first FIND one -- and five gateways carrying byte-identical copies of gw_hdr_val are the
83// estate's standing proof of what happens when a small helper is copied once per consumer.
84// It deliberately does NOT parse a general header set (gw_hdr_val already does that, in five places). It
85// answers exactly one question: what deadline did this request arrive carrying?
86
87// The key length is DERIVED from DL_HDR, never hand-counted. A HAND-COUNTED LENGTH BESIDE A STRING
88// LITERAL IS A SECOND COPY OF THAT LITERAL'S SHAPE, AND THE TWO DRIFT SILENTLY -- rename the header,
89// forget the number, and the parser reads the wrong window while still compiling and still looking fine.
90func dl_keylen() -> i64 {
91 var i: i64 = 0
92 while DL_HDR[i] != (0 as u8) {
93 if (DL_HDR[i] as i64) == 58 { return i + 1 }
94 i = i + 1
95 }
96 return i
97}
98func dl_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
99
100// Case-INSENSITIVE, because HTTP field names are (RFC 9110 s5.1). Two consequences, both wanted: a hop
101// that re-cased the header still has its deadline honoured, and a duplicate it left behind is still
102// recognisable to a rewriter that means to strip it.
103func dl_is_hdr_line(buf: *u8, p: i64, n: i64) -> i64 {
104 let kl: i64 = dl_keylen()
105 var i: i64 = 0
106 while i < kl {
107 if p + i >= n { return 0 }
108 if dl_lc(buf[p+i] as i64) != dl_lc(DL_HDR[i] as i64) { return 0 }
109 i = i + 1
110 }
111 return 1
112}
113
114// The deadline this request ARRIVED with, or DL_ABSENT when it declared none.
115// HEADER BLOCK ONLY, and that bound is load-bearing rather than tidy: the scan stops at the blank line,
116// so a deadline-shaped string inside a POST body can never be read as a header. On a public front door the
117// body is attacker-controlled, and while a request that could SHORTEN its own deadline is merely odd, one
118// that could LENGTHEN it is a bypass of the very budget this file exists to enforce.
119// FIRST match wins -- the same rule every first-match header reader in the estate already follows, so a
120// duplicated header resolves here exactly as it resolves downstream.
121func dl_scan_request(buf: *u8, n: i64) -> i64 {
122 var he: i64 = 0 - 1
123 var i: i64 = 0
124 while i + 3 < n {
125 if (buf[i] as i64)==13 { if (buf[i+1] as i64)==10 { if (buf[i+2] as i64)==13 { if (buf[i+3] as i64)==10 { he = i + 4 } } } }
126 if he >= 0 { i = n } else { i = i + 1 }
127 }
128 var hend: i64 = n
129 if he >= 0 { hend = he - 2 }
130 let kl: i64 = dl_keylen()
131 var p: i64 = 0
132 while p < hend {
133 var le: i64 = p
134 var found: i64 = 0
135 while found == 0 {
136 if le + 1 >= hend { le = hend; found = 1 }
137 else { if (buf[le] as i64)==13 { if (buf[le+1] as i64)==10 { found = 1 } else { le = le + 1 } } else { le = le + 1 } }
138 }
139 if dl_is_hdr_line(buf, p, n) == 1 {
140 let vs: i64 = p + kl
141 if vs <= le { return dl_parse_ms(((buf as i64) + vs) as *u8, le - vs) }
142 }
143 p = le + 2
144 }
145 return DL_ABSENT
146}
147// milliseconds left. Meaningless unless the deadline came from dl_parse_ms on THIS host's boot.
148func dl_remaining_ms(deadline_ms: i64, now_ms: i64) -> i64 {
149 if deadline_ms == DL_ABSENT { return DL_ABSENT }
150 return deadline_ms - now_ms
151}
152
153func dl_state(deadline_ms: i64, now_ms: i64) -> i64 {
154 if deadline_ms == DL_ABSENT { return DL_S_NONE }
155 if deadline_ms <= now_ms { return DL_S_EXPIRED }
156 return DL_S_LIVE
157}
158
159// THE CLAMP. Returns how long the caller may actually wait, given the wait it already wanted.
160// no deadline -> want, UNCHANGED (never invent a budget the request did not ask for)
161// expired -> 0 (do not start work that cannot be delivered; the caller decides what 0 means)
162// live -> min(want, remaining)
163// It can only ever SHORTEN. That is what makes it a composition of the caller's own timeout rather than a
164// rival to it, and it is why adopting this needs no coordination with whatever ruler set `want`.
165func dl_wait_ms(deadline_ms: i64, now_ms: i64, want_ms: i64) -> i64 {
166 if want_ms < 0 { return 0 }
167 let st: i64 = dl_state(deadline_ms, now_ms)
168 if st == DL_S_NONE { return want_ms }
169 if st == DL_S_EXPIRED { return 0 }
170 let rem: i64 = deadline_ms - now_ms
171 if rem < want_ms { return rem }
172 return want_ms
173}