nx_h2_serve_multi.nx source
↩ module page · 264 lines · 14485 B
1// nx_h2_serve_multi.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under the B1
2// rung(6) PERSISTENT-MULTI-STREAM step of the R4-H2 HTTP/2-transport ladder), NOT
3// credited as team self-authoring (back-fill via the emitter-of-emitters once the
4// daemon-from-spec organ exists -- X-AUT-006c/e/f).
5//
6// THE PERSISTENT MULTI-STREAM ALPN-h2 SERVER CONNECTION-DRIVER organ (single
7// responsibility: SERVE >1 REQUEST STREAM on ONE already-accepted, KEPT-ALIVE h2
8// connection over OUR sovereign TLS 1.3). This is the EVOLUTION of nx_h2_serve.nx
9// h2_serve_connection, which serves exactly ONE stream then sends GOAWAY + closes
10// (so each external curl --http2 GET needs a FRESH TCP connection; num_connects=1
11// per request). The persistent driver here:
12// - does NOT send GOAWAY after each stream (GOAWAY ends the whole connection),
13// - LOOPS reading TLS records, COMPACTING consumed frame bytes out of a bounded
14// buffer (so the buffer survives many sequential requests), responding to EACH
15// request HEADERS frame on ITS OWN client-chosen odd stream id (1,3,5,...),
16// - stops when (a) the client closes the connection (read EOF -- the clean end
17// of a sequential keep-alive burst), (b) the client sends GOAWAY, or (c) the
18// served count reaches the caller's max_streams cap (honest backstop), and
19// - sends ONE GOAWAY(last_stream_id) at the very end, then the LM-023 graceful
20// shutdown(SHUT_WR)+drain so the peer reads every response (close-with-unread
21// RX would RST the peer).
22// MEASURED EXCEED (vs h2_serve_connection): an external curl given K same-origin
23// URLs REUSES ONE h2 connection (total num_connects across the K transfers = 1)
24// where the single-stream driver forces K separate connections. The persistent
25// driver thus serves the SAME K requests on 1/K the TCP connections.
26//
27// WHY A NEW ORGAN (rule 9 single-responsibility + rule 25 build-don't-strip):
28// h2_serve_connection is GATED GREEN and REUSED by the proven N=3 daemon; it is
29// left BYTE-UNTOUCHED. Persistent multi-stream is a DISTINCT connection
30// lifecycle (keep-alive read loop with frame compaction + per-stream response +
31// single terminal GOAWAY) -- folding it into the single-stream driver would
32// collapse "serve-one-then-close AND serve-many-keep-alive" into one organ (the
33// 'and' split-test fails). So this organ is a SIBLING driver composing the same
34// proven framing + tls transport stones.
35//
36// DRIVER CONTRACT:
37// h2_serve_connection_multi(ss_fd, srand, sxpriv, cert_der, cert_len, ed_priv,
38// body, blen, max_streams) -> i64
39// The session is established INSIDE via run_h2_ed25519 (handshake inputs are
40// caller args; single-responsibility -- caller hands an already-ACCEPTED fd +
41// per-connection handshake entropy). Returns the COUNT of streams served
42// (>=1) on a clean serve, or the SAME honest negative codes the proven driver
43// family returns: handshake <=0 propagated; -50/-51 record/recv; -53 non-GET
44// or zero-served; -57/-58 send; -60..-65 settings/ack/budget; -100 bad preface.
45//
46// FOUNDED ON (composes, each imported EXACTLY ONCE -- the build's include-guard
47// makes repeat transitive imports idempotent, VERIFIED by the proven daemon):
48// - nx_syscalls.nx: sys_write/read/shutdown/mmap.
49// - nx_h2_server.nx: framing (h2_frame_write_*/h2_check_preface/
50// h2_frame_read_header/h2_decode_request/H2_METHOD_GET), transitively splicing
51// nx_h2_conformance -> nx_h2_frame / nx_hpack / nx_h2_flow / nx_h2_stream.
52// - nx_tls13_server_session_run_h2.nx: the ALPN-h2 Ed25519 handshake +
53// Tls13ServerSession.
54// - nx_tls13_server_session_app_data.nx: app_send / app_recv.
55// - nx_tls13_read_record_from_fd.nx: the one-record reader.
56//
57// license_tier: INDEPENDENT_REDERIVE
58// genealogy_id: international-research-sources/ietf/rfc_9113 (s5 multiplexing) + rfc_7541 + rfc_8446 + rfc_7301
59// lineage_id: nishi_h2_serve_multi_b1r6
60
61import "nx_syscalls.nx"
62import "nx_h2_server.nx"
63import "nx_tls13_server_session_run_h2.nx"
64import "nx_tls13_server_session_app_data.nx"
65import "nx_tls13_read_record_from_fd.nx"
66const NX_MAGIC_16384: i64 = 16384
67const NX_MAGIC_16645: i64 = 16645
68const NX_MAGIC_2048: i64 = 2048
69
70// SETTINGS identifier for SETTINGS_MAX_CONCURRENT_STREAMS (RFC 9113 §6.5.2).
71const NX_H2_SETTINGS_MAX_CONCURRENT_STREAMS: i64 = 0x03
72// The value we ADVERTISE to clients in our connection-preamble SETTINGS frame: the
73// max number of streams we let a peer have open concurrently. 100 = the de-facto
74// h2 default (nghttp2's SETTINGS_MAX_CONCURRENT_STREAMS default) -- a sane initial
75// cap (rule 11: a named const, not a buried literal). NB: this rung only
76// ADVERTISES the limit (RFC 9113 §6.5.2 says a server SHOULD); ENFORCEMENT
77// (RST_STREAM REFUSED_STREAM on the N+1th concurrent stream from a misbehaving
78// client) is the follow-on R4-H2-011.
79const NX_H2_MAX_CONCURRENT_STREAMS: i64 = 100
80
81// ---- write a fully-built plaintext h2 buffer as ONE encrypted TLS record to the
82// socket (encrypt via app_send, then write-all the record). Returns 0 ok,
83// or the named negative the proven driver uses (-57 encrypt, -58 socket). ----
84func msrv_send_record(ss: *Tls13ServerSession, ss_fd: i64, plain: *u8, plen: i64) -> i64 {
85 let outrec: *u8 = sys_mmap(plen + 64)
86 let sn: i64 = nx_tls13_server_session_app_send(ss, plain, plen, outrec, plen + 64)
87 if sn < 0 { return 0 - 57 }
88 var w: i64 = 0
89 while w < sn {
90 let k: i64 = sys_write(ss_fd, (outrec as i64 + w) as *u8, sn - w)
91 if k <= 0 { return 0 - 58 }
92 w = w + k
93 }
94 return 0
95}
96
97// ---- respond to ONE request stream: HEADERS(:status 200, END_HEADERS) +
98// DATA(body, END_STREAM). NO GOAWAY (the connection stays alive for the next
99// stream) -- this is the multi-stream difference from h2_serve_connection. ----
100func msrv_respond_stream(ss: *Tls13ServerSession, ss_fd: i64, sid: i64, body: *u8, blen: i64) -> i64 {
101 let resp: *u8 = sys_mmap(blen + 256)
102 let hblk: *u8 = sys_mmap(8); hblk[0] = 0x88 as u8 // HPACK indexed :status 200
103 var ro: i64 = h2_frame_write_headers(resp, 0, sid, 0x04, hblk, 1) // END_HEADERS
104 if ro < 0 { return ro }
105 ro = h2_frame_write_data(resp, ro, sid, 0x01, body, blen) // DATA END_STREAM
106 if ro < 0 { return ro }
107 return msrv_send_record(ss, ss_fd, resp, ro)
108}
109
110// ---- the sovereign PERSISTENT MULTI-STREAM ALPN-h2 server connection-driver ----
111func h2_serve_connection_multi(
112 ss_fd: i64, srand: *u8, sxpriv: *u8,
113 cert_der: *u8, cert_len: i64, ed_priv: *u8,
114 body: *u8, blen: i64, max_streams: i64
115) -> i64 {
116 let sret: i64 = nx_tls13_server_session_run_h2_ed25519(
117 ss_fd, srand, sxpriv, cert_der, cert_len, ed_priv)
118 if sret <= 0 { return sret } // handshake failed -> propagate
119 let ss: *Tls13ServerSession = sret as *Tls13ServerSession
120
121 // ---- 1. send our SETTINGS first (advertising SETTINGS_MAX_CONCURRENT_STREAMS
122 // per RFC 9113 §6.5.2) -- curl waits for our SETTINGS before sending its
123 // request HEADERS. (Was an EMPTY SETTINGS frame; now a one-param frame
124 // declaring our concurrency limit. A well-behaved client honours it;
125 // enforcement against a misbehaving client is R4-H2-011.) ----
126 let sbuf: *u8 = sys_mmap(64)
127 let sbn: i64 = h2_frame_write_settings_param(
128 sbuf, 0, NX_H2_SETTINGS_MAX_CONCURRENT_STREAMS, NX_H2_MAX_CONCURRENT_STREAMS)
129 let s0: i64 = msrv_send_record(ss, ss_fd, sbuf, sbn)
130 if s0 < 0 { return 0 - 60 }
131
132 // ---- 2. keep-alive read loop: accumulate frames across records, ACK client
133 // SETTINGS once, respond to EACH HEADERS request on its own stream id,
134 // COMPACTING consumed bytes so the bounded buffer survives many
135 // sequential requests. Stop on client-close / client-GOAWAY / cap. ----
136 let frames: *u8 = sys_mmap(NX_MAGIC_16384)
137 var flen: i64 = 0
138 var preface_ok: i64 = 0
139 var acked: i64 = 0
140 var served: i64 = 0
141 var last_sid: i64 = 0
142 var done: i64 = 0
143 var budget: i64 = 0
144 let m: *i64 = sys_mmap(8) as *i64
145 let po: *i64 = sys_mmap(8) as *i64
146 let pl: *i64 = sys_mmap(8) as *i64
147 let rec: *u8 = sys_mmap(NX_MAGIC_16645)
148 let ob: *u8 = sys_mmap(NX_MAGIC_16645)
149 let rlen: *i64 = sys_mmap(8) as *i64
150 let rtype: *i64 = sys_mmap(8) as *i64
151 let rflags: *i64 = sys_mmap(8) as *i64
152 let rsid: *i64 = sys_mmap(8) as *i64
153 while done == 0 {
154 if budget >= 512 { done = 1 } // honest record-count cap
155 if done == 0 {
156 budget = budget + 1
157 let rtot: i64 = nx_tls13_read_record_from_fd(ss_fd, rec, NX_MAGIC_16645)
158 if rtot <= 0 { done = 1 } // EOF/err -> client closed conn
159 if done == 0 {
160 var on: i64 = nx_tls13_server_session_app_recv(ss, rec, rtot, ob, NX_MAGIC_16645)
161 // a non-application-data record (e.g. the peer's TLS close_notify
162 // alert at end-of-burst) or a decrypt error: if we have ALREADY
163 // served >=1 stream the peer is winding the connection down -> treat
164 // as a GRACEFUL end (stop, return served; on=0 skips processing this
165 // record). served==0 -> a genuine transport failure -> honest -51.
166 if on < 0 {
167 if served > 0 { done = 1; on = 0 } else { return 0 - 51 }
168 }
169 var ai: i64 = 0
170 while ai < on {
171 if flen >= NX_MAGIC_16384 { return 0 - 63 }
172 frames[flen] = ob[ai]
173 flen = flen + 1
174 ai = ai + 1
175 }
176 // consume the 24-byte connection preface once (compact it out).
177 if preface_ok == 0 {
178 if flen >= 24 {
179 if h2_check_preface(frames, 0, 24) != 1 { return 0 - 100 }
180 var ci: i64 = 24
181 var di: i64 = 0
182 while ci < flen { frames[di] = frames[ci]; di = di + 1; ci = ci + 1 }
183 flen = flen - 24
184 preface_ok = 1
185 }
186 }
187 if preface_ok == 1 {
188 var cur: i64 = 0
189 while cur < flen {
190 let pstart: i64 = h2_frame_read_header(frames, cur, flen, rlen, rtype, rflags, rsid)
191 if pstart < 0 { cur = flen; break } // incomplete header -> need more
192 let plen2: i64 = rlen[0]
193 if pstart + plen2 > flen {
194 cur = cur // payload not fully arrived;
195 break // keep [cur..flen) for next record
196 }
197 let ftype: i64 = rtype[0]
198 let fflags: i64 = rflags[0]
199 let fsid: i64 = rsid[0]
200 if ftype == 0x04 { // SETTINGS
201 if (fflags & 0x01) == 0 { // not an ACK -> ACK once
202 if acked == 0 {
203 let abuf: *u8 = sys_mmap(32)
204 let abn: i64 = h2_frame_write_settings_ack(abuf, 0)
205 let ar: i64 = msrv_send_record(ss, ss_fd, abuf, abn)
206 if ar < 0 { return 0 - 64 }
207 acked = 1
208 }
209 }
210 }
211 if ftype == 0x07 { done = 1 } // client GOAWAY -> wind down
212 if ftype == 0x01 { // HEADERS = a request
213 let dr: i64 = h2_decode_request(frames, pstart, pstart + plen2, m, po, pl)
214 if dr < 0 { return dr } // honest propagate (no fabrication)
215 if m[0] != H2_METHOD_GET { return 0 - 53 }
216 let rr: i64 = msrv_respond_stream(ss, ss_fd, fsid, body, blen)
217 if rr < 0 { return rr }
218 served = served + 1
219 last_sid = fsid
220 if served >= max_streams { done = 1 } // honest cap reached
221 }
222 cur = pstart + plen2
223 }
224 // compact consumed [0..cur) out so the buffer survives the next request.
225 if cur > 0 {
226 var si: i64 = cur
227 var di2: i64 = 0
228 while si < flen { frames[di2] = frames[si]; di2 = di2 + 1; si = si + 1 }
229 flen = flen - cur
230 }
231 }
232 }
233 }
234 }
235
236 // ---- 3. ONE terminal GOAWAY(last_stream_id, NO_ERROR) for the whole
237 // connection, then LM-023 graceful shutdown(SHUT_WR)+drain. ----
238 if served > 0 {
239 let gbuf: *u8 = sys_mmap(64)
240 let nodbg: *u8 = sys_mmap(8)
241 let gn: i64 = h2_frame_write_goaway(gbuf, 0, last_sid, 0x00, nodbg, 0)
242 if gn > 0 { msrv_send_record(ss, ss_fd, gbuf, gn) }
243 }
244 sys_shutdown(ss_fd, 1) // 1 = SHUT_WR (Linux) -> FIN
245 let dbuf: *u8 = sys_mmap(NX_MAGIC_2048)
246 var dn: i64 = 1
247 var dc: i64 = 0
248 while dn > 0 {
249 if dc >= 64 { dn = 0 } // safety cap on drain reads
250 dc = dc + 1
251 let r: i64 = sys_read(ss_fd, dbuf, NX_MAGIC_2048)
252 if r <= 0 { dn = 0 } // EOF (peer closed) -> done
253 }
254 if served == 0 { return 0 - 53 } // no request served honestly
255 return served
256}
257
258// ---- self-gate main(): LIBRARY organ (real proof = daemon + external curl).
259// main() prints a scaffold label and exits 0 so the organ BUILDS standalone. ----
260func main() -> i64 {
261 sys_write(1, "nx_h2_serve_multi: SCAFFOLD library -- h2_serve_connection_multi (B1 rung 6 persistent multi-stream), proof = daemon + external curl\n" as *u8, 130)
262 sys_exit(0)
263 return 0
264}