nx_h2_serve.nx source
↩ module page · 248 lines · 13338 B
1// nx_h2_serve.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under the B1
2// rung(5) connection-driver LIFT of the R4-H2 HTTP/2-transport ladder), NOT
3// credited as team self-authoring (back-fill via the emitter-of-emitters once
4// the daemon-from-spec organ exists -- X-AUT-006c/e/f).
5//
6// THE REUSABLE ALPN-h2 SERVER CONNECTION-DRIVER organ (single responsibility:
7// SERVE ONE already-accepted h2 connection over OUR sovereign TLS 1.3). This is
8// the VERBATIM LIFT of _h2_serve_curl_daemon.nx's PROVEN hcd_run_server (renamed
9// h2_serve_connection, the param scfd -> ss_fd) -- it is NOT a rewrite (BL-012
10// compose-not-duplicate): the loop already serves an EXTERNAL curl --http2 GET
11// at http_version=2 response_code=200 byte-exact body (knowledge/status/
12// h2_nx_h2_serve_curl.log). The daemon scaffold now SHRINKS to import this organ
13// and call the lifted driver in a counted accept loop.
14//
15// WHY A NEW ORGAN (rule 9 single-responsibility + rule 15 DRY, not nx_h2_server):
16// nx_h2_server.nx is the PURE in-memory FRAMER (h2_decode_request /
17// h2_server_send_response); its own header (lines 33-35) says it deliberately
18// does NOT import the tls13 record/session transport because "the in-memory
19// gate needs only the framing". The connection DRIVER is a DIFFERENT
20// responsibility: it owns the live TLS-over-socket transport loop (send our
21// SETTINGS -> read-record loop accumulating frames -> ACK client SETTINGS ->
22// parse HEADERS -> respond HEADERS 0x88 + DATA + GOAWAY -> shutdown(SHUT_WR) +
23// drain). Folding transport into nx_h2_server would force its framing gate to
24// import the whole tls13 server-session stack and collapse "framing AND
25// live-serving" into one organ (the 'and' split-test fails). So this organ
26// COMPOSES nx_h2_server for framing + the tls server transport stones.
27//
28// DRIVER CONTRACT (verbatim -- NO new return semantics):
29// h2_serve_connection(ss_fd, srand, sxpriv, cert_der, cert_len, ed_priv,
30// body, blen) -> i64
31// The session is established INSIDE via
32// nx_tls13_server_session_run_h2_ed25519(ss_fd, ...) (handshake inputs are
33// caller args; the established *Tls13ServerSession is NOT a caller arg --
34// the caller hands an already-ACCEPTED fd + the per-connection handshake
35// entropy, single-responsibility). Returns 0 on a clean serve (handshake
36// h2 + parsed GET + wrote :status 200 + graceful FIN), or the SAME honest
37// negative codes the proven driver returns: handshake <=0 propagated;
38// -50/-51 record/recv; -53 non-GET; -57/-58 send; -60..-65 settings/ack/
39// budget; -100 bad preface.
40//
41// FOUNDED ON (composes, each imported EXACTLY ONCE -- RC6 double-import is BENIGN
42// here and VERIFIED: nx_tls13_server_session_run_h2.nx ALREADY imports
43// nx_syscalls.nx AND nx_tls13_read_record_from_fd.nx (its lines 38,41) and the
44// proven daemon ALSO imports both directly yet builds+runs GREEN -- the build's
45// include-guard makes repeat imports idempotent, no duplicate-symbol):
46// - nx_syscalls.nx: sys_socket/bind/listen/accept/shutdown/read/write/mmap
47// (sys_accept pulled transitively from nx_syscalls_x86_64.nx, sys_shutdown
48// at nx_syscalls.nx:514).
49// - nx_h2_server.nx: framing -- transitively splices nx_h2_conformance ->
50// nx_h2_frame (h2_frame_write_settings_empty/_settings_ack/_headers/_data/
51// _goaway, h2_check_preface, h2_frame_read_header) / nx_hpack / nx_h2_flow /
52// nx_h2_stream -- plus h2_decode_request + H2_METHOD_GET defined directly in
53// nx_h2_server.nx.
54// - nx_tls13_server_session_run_h2.nx: nx_tls13_server_session_run_h2_ed25519
55// + Tls13ServerSession (the ALPN-h2 Ed25519 handshake).
56// - nx_tls13_server_session_app_data.nx: app_send / app_recv.
57// - nx_tls13_read_record_from_fd.nx: the one-record reader.
58// The driver does NOT need nx_base64/x509/csprng/x25519/ed25519 -- those are
59// CERT-MINTING + PEM-EXPORT concerns that STAY in the daemon scaffold (the daemon
60// owns key/cert/socket lifecycle; this organ serves ONE connection).
61//
62// license_tier: INDEPENDENT_REDERIVE
63// genealogy_id: international-research-sources/ietf/rfc_9113 + rfc_7541 + rfc_8446 + rfc_7301 + rfc_5280
64// lineage_id: nishi_h2_serve_b1r5
65
66import "nx_syscalls.nx"
67import "nx_h2_server.nx"
68import "nx_tls13_server_session_run_h2.nx"
69import "nx_tls13_server_session_app_data.nx"
70import "nx_tls13_read_record_from_fd.nx"
71const K_MAGIC_16384: i64 = 16384
72const K_MAGIC_16645: i64 = 16645
73const K_MAGIC_2048: i64 = 2048
74
75// ---- the sovereign ALPN-h2 SERVER connection-driver (ONE already-accepted
76// connection) -- VERBATIM LIFT of _h2_serve_curl_daemon.nx hcd_run_server
77// (renamed h2_serve_connection; the param scfd -> ss_fd; every internal
78// socket-fd reference renamed scfd -> ss_fd consistently -- a pure rename,
79// the logic is byte-for-byte the PROVEN driver). ----
80// Handshake (SELECT "h2" via emit_ee_alpn + Ed25519 CV), then run the REAL h2
81// server connection preamble: send our SETTINGS, read TLS records in a LOOP
82// accumulating h2 frames (curl sends preface+SETTINGS+WINDOW_UPDATE first and
83// the request HEADERS in a LATER record, awaiting the server's SETTINGS), ACK
84// curl's SETTINGS, parse the request HEADERS (:method/:path) when it arrives,
85// send HEADERS(:status 200)+DATA(END_STREAM)+GOAWAY on curl's stream, then
86// shutdown(SHUT_WR)+drain for a graceful FIN (close-with-unread-data was RSTing
87// curl's read of the response). Returns 0 ok; a NAMED negative code on failure
88// (never fabricates a method). VERIFIED LIVE: curl 8.5.0 --http2 --cacert ->
89// http_version=2 response_code=200 + byte-exact body, through OUR 100%-sovereign
90// server (no nghttp2/openssl/gcc on our side; curl = last-mile interop peer).
91func h2_serve_connection(
92 ss_fd: i64, srand: *u8, sxpriv: *u8,
93 cert_der: *u8, cert_len: i64, ed_priv: *u8,
94 body: *u8, blen: i64
95) -> i64 {
96 let sret: i64 = nx_tls13_server_session_run_h2_ed25519(
97 ss_fd, srand, sxpriv, cert_der, cert_len, ed_priv)
98 if sret <= 0 { return sret } // handshake failed -> propagate honest <0
99 let ss: *Tls13ServerSession = sret as *Tls13ServerSession
100
101 // ---- 1. send server SETTINGS (empty) immediately. A real h2 server sends
102 // its SETTINGS as the first frame; curl waits for it before sending
103 // its request HEADERS (this is why the one-record loopback read
104 // failed: curl's first record is only preface+SETTINGS+WINDOW_UPDATE,
105 // the HEADERS arrive in a LATER record). ----
106 let sbuf: *u8 = sys_mmap(64)
107 let sbn: i64 = h2_frame_write_settings_empty(sbuf, 0)
108 let sorec: *u8 = sys_mmap(sbn + 64)
109 let ssn: i64 = nx_tls13_server_session_app_send(ss, sbuf, sbn, sorec, sbn + 64)
110 if ssn < 0 { return 0 - 60 }
111 var sw: i64 = 0
112 while sw < ssn {
113 let sk: i64 = sys_write(ss_fd, (sorec as i64 + sw) as *u8, ssn - sw)
114 if sk <= 0 { return 0 - 61 }
115 sw = sw + sk
116 }
117
118 // ---- 2. read-loop: accumulate decrypted h2 frames across TLS records, ACK
119 // curl's SETTINGS, and stop on the FIRST HEADERS frame (the request).
120 // This is the REAL h2 server connection preamble vs the loopback's
121 // one-shot single-record assumption. ----
122 let frames: *u8 = sys_mmap(K_MAGIC_16384)
123 var flen: i64 = 0
124 var preface_ok: i64 = 0
125 var acked: i64 = 0
126 var got_headers: i64 = 0
127 var req_sid: i64 = 1
128 let m: *i64 = sys_mmap(8) as *i64
129 let po: *i64 = sys_mmap(8) as *i64
130 let pl: *i64 = sys_mmap(8) as *i64
131 let rec: *u8 = sys_mmap(K_MAGIC_16645)
132 let ob: *u8 = sys_mmap(K_MAGIC_16645)
133 let rlen: *i64 = sys_mmap(8) as *i64
134 let rtype: *i64 = sys_mmap(8) as *i64
135 let rflags: *i64 = sys_mmap(8) as *i64
136 let rsid: *i64 = sys_mmap(8) as *i64
137 var budget: i64 = 0
138 while got_headers == 0 {
139 if budget >= 64 { return 0 - 62 } // honest record-count cap
140 budget = budget + 1
141 let rtot: i64 = nx_tls13_read_record_from_fd(ss_fd, rec, K_MAGIC_16645)
142 if rtot <= 0 { return 0 - 50 }
143 let on: i64 = nx_tls13_server_session_app_recv(ss, rec, rtot, ob, K_MAGIC_16645)
144 if on < 0 { return 0 - 51 }
145 var ai: i64 = 0
146 while ai < on {
147 if flen >= K_MAGIC_16384 { return 0 - 63 }
148 frames[flen] = ob[ai]
149 flen = flen + 1
150 ai = ai + 1
151 }
152 if preface_ok == 0 {
153 if flen >= 24 {
154 if h2_check_preface(frames, 0, 24) != 1 { return 0 - 100 }
155 preface_ok = 1
156 }
157 }
158 if preface_ok == 1 {
159 var cur: i64 = 24
160 while cur < flen {
161 let pstart: i64 = h2_frame_read_header(frames, cur, flen, rlen, rtype, rflags, rsid)
162 if pstart < 0 { cur = cur; break } // incomplete header -> need more
163 let plen: i64 = rlen[0]
164 if pstart + plen > flen { cur = cur; break } // payload not fully arrived
165 let ftype: i64 = rtype[0]
166 let fflags: i64 = rflags[0]
167 let fsid: i64 = rsid[0]
168 if ftype == 0x04 { // SETTINGS
169 if (fflags & 0x01) == 0 { // not an ACK -> ACK curl's settings once
170 if acked == 0 {
171 let abuf: *u8 = sys_mmap(32)
172 let abn: i64 = h2_frame_write_settings_ack(abuf, 0)
173 let aorec: *u8 = sys_mmap(abn + 64)
174 let asn: i64 = nx_tls13_server_session_app_send(ss, abuf, abn, aorec, abn + 64)
175 if asn < 0 { return 0 - 64 }
176 var aw: i64 = 0
177 while aw < asn {
178 let ak: i64 = sys_write(ss_fd, (aorec as i64 + aw) as *u8, asn - aw)
179 if ak <= 0 { return 0 - 65 }
180 aw = aw + ak
181 }
182 acked = 1
183 }
184 }
185 }
186 if ftype == 0x01 { // HEADERS = the request
187 let dr: i64 = h2_decode_request(frames, pstart, pstart + plen, m, po, pl)
188 if dr < 0 { return dr } // honest propagate (no fabrication)
189 req_sid = fsid
190 got_headers = 1
191 }
192 cur = pstart + plen
193 if got_headers == 1 { cur = flen; break }
194 }
195 }
196 }
197 if m[0] != H2_METHOD_GET { return 0 - 53 }
198
199 // ---- 3. respond on curl's stream: HEADERS(:status 200) + DATA(END_STREAM)
200 // + GOAWAY(NO_ERROR). The GOAWAY tells curl the connection is closing
201 // gracefully so it does not try to keep it alive for reuse. ----
202 let resp: *u8 = sys_mmap(512)
203 let hblk: *u8 = sys_mmap(8); hblk[0] = 0x88 as u8 // HPACK indexed :status 200
204 var ro: i64 = h2_frame_write_headers(resp, 0, req_sid, 0x04, hblk, 1) // END_HEADERS
205 if ro < 0 { return ro }
206 ro = h2_frame_write_data(resp, ro, req_sid, 0x01, body, blen) // DATA END_STREAM
207 if ro < 0 { return ro }
208 let nodbg: *u8 = sys_mmap(8)
209 ro = h2_frame_write_goaway(resp, ro, req_sid, 0x00, nodbg, 0) // GOAWAY NO_ERROR
210 if ro < 0 { return ro }
211 let outrec: *u8 = sys_mmap(ro + 64)
212 let sn: i64 = nx_tls13_server_session_app_send(ss, resp, ro, outrec, ro + 64)
213 if sn < 0 { return 0 - 57 }
214 var w: i64 = 0
215 while w < sn {
216 let k: i64 = sys_write(ss_fd, (outrec as i64 + w) as *u8, sn - w)
217 if k <= 0 { return 0 - 58 }
218 w = w + k
219 }
220
221 // ---- 4. GRACEFUL close: shutdown(SHUT_WR) sends a FIN so curl receives the
222 // FULL response before teardown, then drain curl's remaining inbound
223 // bytes (its SETTINGS-ACK / WINDOW_UPDATE / FIN) to EOF so the final
224 // close() is a clean FIN -- NOT a RST. (close() with unread RX data
225 // was sending a TCP RST that reset curl's read of the response:
226 // "Recv failure: Connection reset by peer".) ----
227 sys_shutdown(ss_fd, 1) // 1 = SHUT_WR (Linux)
228 let dbuf: *u8 = sys_mmap(K_MAGIC_2048)
229 var dn: i64 = 1
230 var dc: i64 = 0
231 while dn > 0 {
232 if dc >= 64 { dn = 0 } // safety cap on drain reads
233 dc = dc + 1
234 let r: i64 = sys_read(ss_fd, dbuf, K_MAGIC_2048)
235 if r <= 0 { dn = 0 } // EOF (curl closed) or error -> done
236 }
237 return 0
238}
239
240// ---- self-gate main(): this organ is primarily a LIBRARY (the real proof is the
241// daemon run + external curl). main() just prints a scaffold-label line and
242// exits 0 so the organ BUILDS standalone (no sockets stood up here -- a
243// bind/accept self-test belongs to the daemon, single-responsibility). ----
244func main() -> i64 {
245 sys_write(1, "nx_h2_serve: SCAFFOLD library -- h2_serve_connection lifted (B1 rung 5), proof = daemon + external curl\n" as *u8, 102)
246 sys_exit(0)
247 return 0
248}