code wiki / (root) / nx_h2_serve_multi.nx

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}