code wiki / _hdl_build / _h2_serve_curl_daemon.nx
_h2_serve_curl_daemon.nx source
↩ module page · 364 lines · 18096 B
1// _h2_serve_curl_daemon.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under
2// the B1 rung(4) external-curl ALPN-h2 INTEROP workflow of the R4-H2 HTTP/2-
3// transport ladder, REFACTORED at rung(5)), NOT credited as team self-authoring
4// (back-fill via the emitter-of-emitters once the daemon-from-spec organ exists).
5//
6// RUNG(5) REFACTOR: the per-connection driver (was hcd_run_server) has been
7// LIFTED VERBATIM into the reusable organ runtime/nx_h2_serve.nx
8// (h2_serve_connection) -- BL-012 compose-not-duplicate. This daemon now SHRINKS
9// to: mint cert + write PEM + bind/listen ONCE, then a COUNTED accept loop of
10// N=HCD_N (=3) that, per connection, regenerates fresh handshake entropy, runs
11// the handshake-then-serve via the lifted h2_serve_connection, closes, and
12// stops-on-RED. cert + bind + listen happen ONCE; only accept + serve repeat.
13//
14// THE STANDALONE ALPN-h2 SERVE DAEMON: stands up OUR sovereign ALPN-h2 TLS 1.3
15// SERVER on 127.0.0.1:9443 and serves an EXTERNAL, INDEPENDENT h2 client (curl
16// --http2 with libnghttp2/OpenSSL). It is the SERVER HALF of
17// _h2_serve_loopback_gate.nx with the fork+child-client removed and a real
18// bind/accept loop added; curl is the LAST-MILE interop measuring stick (the
19// sanctioned HTTP last-mile exception), NEVER a substrate -- OUR server side is
20// 100% sovereign nx_cc->nxasm (no gcc/openssl/nghttp2 in our implementation).
21//
22// FLOW (main):
23// - mint a self-signed Ed25519 X.509 leaf (SAN=localhost) -- the SAME
24// sovereign cert path the loopback gate proved (hsl_make_cert), here
25// _hcd_make_cert.
26// - EXPORT that DER cert to a PEM file (/tmp/nx_h2_interop_cert.pem) so an
27// external curl can trust it via --cacert. PEM = ASCII wrapper around the
28// DER: "-----BEGIN CERTIFICATE-----\n" + b64_encode(der) wrapped at 64
29// cols + "-----END CERTIFICATE-----\n", written with sys_openat_wr/write.
30// (There is NO PEM-ENCODER organ; nx_pem.nx only DECODES. We COMPOSE the
31// canonical nx_base64.nx b64_encode primitive rather than reinvent it.)
32// - server x25519 ephemeral + random.
33// - socket / SO_REUSEADDR / bind 127.0.0.1:9443 / listen; print
34// "BOUND 127.0.0.1:9443" so the orchestrator's readiness gate fires.
35// - ACCEPT LOOP (N=HCD_N=3): per connection accept -> fresh handshake entropy
36// -> h2_serve_connection (the LIFTED driver in nx_h2_serve.nx: handshake
37// SELECT "h2" + Ed25519 CV -> send our SETTINGS -> read-loop accumulate
38// frames + ACK + parse HEADERS -> respond :status 200 + body + GOAWAY ->
39// shutdown(SHUT_WR)+drain) -> close -> repeat; exit 0 after N served,
40// stop-on-RED on any non-zero driver code.
41//
42// HONEST verdict discipline: this organ's job is to BIND + SERVE one external
43// h2 request. GREEN (server side) iff the handshake completed, a request frame
44// was parsed, and the :status-200 response was written to the socket. The
45// EXTERNAL curl judgement ("* using HTTP/2" + "ALPN: server accepted h2" +
46// "< HTTP/2 200") is the NEXT (interop) phase, judged from curl's OWN -v output,
47// not from this organ's exit code. curl-specific HPACK representations (Huffman
48// / dynamic-table) may trip h2_server_recv_request's literal walker -> that is a
49// real, NAMED interop finding, never something to fake past.
50//
51// FOUNDED ON (composes, each imported EXACTLY ONCE, RC6 double-import avoided).
52// RUNG(5): the transport stones (nx_h2_server + the 3 tls13 modules) are now
53// re-exported TRANSITIVELY through nx_h2_serve.nx -- the daemon imports ONLY the
54// driver organ + the cert/PEM/socket stones it still owns:
55// - nx_h2_serve.nx: h2_serve_connection (the LIFTED per-connection driver); it
56// re-exports nx_h2_server (framing) + nx_tls13_server_session_run_h2 +
57// nx_tls13_server_session_app_data + nx_tls13_read_record_from_fd transitively,
58// so the daemon no longer imports those four directly.
59// - nx_x25519_ephemeral / nx_csprng / nx_ed25519_signature: keys + randomness
60// (per-connection x25519 + srand regenerated in the accept loop; ed25519 for
61// the cert).
62// - hub/nx_x509_build / nx_x509 / nx_x509_trust_store: self-signed leaf builder.
63// - nx_base64.nx: b64_encode for the PEM export (the cert-trust interop path).
64//
65// license_tier: INDEPENDENT_REDERIVE
66// genealogy_id: international-research-sources/ietf/rfc_8446 + rfc_7301 + rfc_9113 + rfc_5280 + rfc_4648
67// lineage_id: nishi_h2_serve_curl_daemon_b1r4
68
69import "nx_syscalls.nx"
70import "nx_h2_serve.nx" // B1 rung(5): the LIFTED connection-driver organ
71 // (h2_serve_connection) -- re-exports nx_h2_server
72 // + the 3 tls13 transport stones transitively, so
73 // the daemon no longer imports them directly.
74import "nx_x25519_ephemeral.nx"
75import "nx_csprng.nx"
76import "nx_ed25519_signature.nx"
77import "hub/nx_x509_build.nx"
78import "nx_x509.nx"
79import "nx_x509_trust_store.nx"
80import "nx_base64.nx"
81
82// N connections this daemon serves before exiting (B1 rung(5): N>1 = 3). Each
83// curl --http2 GET is a FRESH connection (our GOAWAY closes the prior one), so
84// the accept loop serves HCD_N independent h2 connections then exits 0.
85const HCD_N: i64 = 3
86
87// 9443 decimal -> hcd_addr does the network-order byte split (htons), so we pass
88// the host integer 9443 directly.
89const HCD_PORT_REAL: i64 = 9443
90
91// ---- public-domain civil-date math (Howard Hinnant) -- reimplemented inline so
92// we don't import bin/nx_cert_gen.nx (which carries its own main()). Copied
93// VERBATIM from _h2_serve_loopback_gate.nx (renamed _hcd_ to avoid any
94// symbol overlap if both organs ever co-link). ----
95func hcd_civil_from_days(days: i64, out_y: *i64, out_m: *i64, out_d: *i64) -> i64 {
96 let z: i64 = days + 719468
97 let era: i64 = if z >= 0 then z / 146097 else (z - 146096) / 146097
98 let doe: i64 = z - era * 146097
99 let yoe: i64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
100 let y: i64 = yoe + era * 400
101 let doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100)
102 let mp: i64 = (5 * doy + 2) / 153
103 let d: i64 = doy - (153 * mp + 2) / 5 + 1
104 var m: i64 = mp + 3
105 if mp < 10 { m = mp + 3 } else { m = mp - 9 }
106 var yy: i64 = y
107 if m <= 2 { yy = y + 1 }
108 out_y[0] = yy; out_m[0] = m; out_d[0] = d
109 return 0
110}
111func hcd_format_generalized_time(unix_sec: i64, out_15: *u8) -> i64 {
112 let days: i64 = unix_sec / 86400
113 let sod: i64 = unix_sec - days * 86400
114 let hh: i64 = sod / 3600
115 let mm: i64 = (sod - hh * 3600) / 60
116 let ss: i64 = sod - hh * 3600 - mm * 60
117 let yp: *i64 = sys_mmap(8) as *i64
118 let mp: *i64 = sys_mmap(8) as *i64
119 let dp: *i64 = sys_mmap(8) as *i64
120 hcd_civil_from_days(days, yp, mp, dp)
121 let y: i64 = yp[0]; let mo: i64 = mp[0]; let d: i64 = dp[0]
122 out_15[0] = ((0x30 + ((y / 1000) % 10)) & 0xff) as u8
123 out_15[1] = ((0x30 + ((y / 100) % 10)) & 0xff) as u8
124 out_15[2] = ((0x30 + ((y / 10) % 10)) & 0xff) as u8
125 out_15[3] = ((0x30 + ( y % 10)) & 0xff) as u8
126 out_15[4] = ((0x30 + (mo / 10)) & 0xff) as u8
127 out_15[5] = ((0x30 + (mo % 10)) & 0xff) as u8
128 out_15[6] = ((0x30 + (d / 10)) & 0xff) as u8
129 out_15[7] = ((0x30 + (d % 10)) & 0xff) as u8
130 out_15[8] = ((0x30 + (hh / 10)) & 0xff) as u8
131 out_15[9] = ((0x30 + (hh % 10)) & 0xff) as u8
132 out_15[10] = ((0x30 + (mm / 10)) & 0xff) as u8
133 out_15[11] = ((0x30 + (mm % 10)) & 0xff) as u8
134 out_15[12] = ((0x30 + (ss / 10)) & 0xff) as u8
135 out_15[13] = ((0x30 + (ss % 10)) & 0xff) as u8
136 out_15[14] = 0x5A as u8 // 'Z'
137 return 0
138}
139
140// Build the 16-byte sockaddr_in for 127.0.0.1:port (AF_INET=2, htons port,
141// 0x0100007f = 127.0.0.1 in network order). Copied VERBATIM (renamed) from
142// _h2_serve_loopback_gate.nx hsl_addr.
143func hcd_addr(out: *u8, port: i64) -> i64 {
144 out[0] = 2 as u8; out[1] = 0 as u8 // AF_INET
145 out[2] = ((port >> 8) & 0xff) as u8 // port hi (network order)
146 out[3] = (port & 0xff) as u8 // port lo
147 out[4] = 127 as u8; out[5] = 0 as u8; out[6] = 0 as u8; out[7] = 1 as u8 // 127.0.0.1
148 var i: i64 = 8
149 while i < 16 { out[i] = 0 as u8; i = i + 1 }
150 return 0
151}
152
153// Generate the self-signed Ed25519 leaf (SAN=localhost). Returns 0 ok with
154// *out_cert_der / *out_cert_len / *out_priv set. The trust store is NOT built
155// here (curl is the external verifier, not OUR store) -- the cert export below
156// hands the same leaf to curl as a PEM. Body copied (renamed) from
157// _h2_serve_loopback_gate.nx hsl_make_cert minus the trust-store steps.
158func hcd_make_cert(
159 out_cert_der: *u8, out_cert_len: *i64, out_priv: *u8
160) -> i64 {
161 // 1. Ed25519 keypair.
162 if nx_csprng_fill(out_priv, 32) != 0 { return 0 - 1 }
163 let pub32: *u8 = sys_mmap(32)
164 if ed25519_pub_from_priv(out_priv, pub32) != 0 { return 0 - 2 }
165
166 // 2. validity window: notBefore = now - 1 day, notAfter = now + 365 days.
167 let now: i64 = sys_now_realtime_sec()
168 let nb: *u8 = sys_mmap(16)
169 let na: *u8 = sys_mmap(16)
170 hcd_format_generalized_time(now - 86400, nb)
171 hcd_format_generalized_time(now + 365 * 86400, na)
172
173 // 3. SAN = "localhost" (parallel arrays for nx_x509_inputs_init).
174 let san_name: *u8 = sys_mmap(16); nx_str_cpy(san_name, "localhost" as *u8)
175 let san_ptrs: *i64 = sys_mmap(8) as *i64; san_ptrs[0] = san_name as i64
176 let san_lens: *i64 = sys_mmap(8) as *i64; san_lens[0] = 9
177 let domain: *u8 = sys_mmap(16); nx_str_cpy(domain, "localhost" as *u8)
178
179 // 4. inputs + build self-signed.
180 let inp: *NxX509BuildInputs = sys_mmap(256) as *NxX509BuildInputs
181 let in_rc: i64 = nx_x509_inputs_init(inp, domain, 9,
182 san_ptrs, san_lens, 1,
183 out_priv, pub32,
184 nb, na, now)
185 if in_rc != NX_X509_OK { return 0 - 3 }
186 let cdn: *i64 = sys_mmap(8) as *i64; cdn[0] = 0
187 let brc: i64 = nx_x509_build_self_signed(inp, out_cert_der, 8192, cdn)
188 if brc != NX_X509_OK { return 0 - 4 }
189 out_cert_len[0] = cdn[0]
190 return 0
191}
192
193// ---- PEM export: write OUR DER cert as a PEM file curl --cacert can trust ----
194// Composes nx_base64.nx b64_encode (the canonical RFC-4648 encoder) -- we do NOT
195// reinvent the alphabet/quantum loop. b64_encode emits ONE unbroken base64
196// stream; we then re-wrap it at 64 cols (PEM/RFC-7468 line length) with '\n'
197// terminators, framed by the BEGIN/END CERTIFICATE armor. Returns 0 ok, <0 on
198// any write/open failure.
199func hcd_write_pem(cert_der: *u8, der_len: i64, path_z: *u8) -> i64 {
200 // 1. base64 the DER into a scratch buffer (4*ceil(n/3) bytes).
201 let b64cap: i64 = 4 * ((der_len + 2) / 3) + 8
202 let b64buf: *u8 = sys_mmap(b64cap)
203 let b64n: i64 = b64_encode(cert_der, der_len, b64buf)
204 if b64n <= 0 { return 0 - 1 }
205
206 // 2. open the PEM file for write (create/trunc, mode 0644 = 0x1a4).
207 let fd: i64 = sys_openat_wr(path_z, 0x1a4)
208 if fd < 0 { return 0 - 2 }
209
210 // 3. BEGIN armor.
211 if sys_write(fd, "-----BEGIN CERTIFICATE-----\n" as *u8, 28) != 28 {
212 sys_close(fd); return 0 - 3
213 }
214
215 // 4. body re-wrapped at 64 cols, each line newline-terminated.
216 var off: i64 = 0
217 while off < b64n {
218 var line: i64 = 64
219 if b64n - off < 64 { line = b64n - off }
220 let w: i64 = sys_write(fd, ((b64buf as i64) + off) as *u8, line)
221 if w != line { sys_close(fd); return 0 - 4 }
222 if sys_write(fd, "\n" as *u8, 1) != 1 { sys_close(fd); return 0 - 5 }
223 off = off + line
224 }
225
226 // 5. END armor.
227 if sys_write(fd, "-----END CERTIFICATE-----\n" as *u8, 26) != 26 {
228 sys_close(fd); return 0 - 6
229 }
230 sys_close(fd)
231 return 0
232}
233
234// ---- the sovereign ALPN-h2 SERVER connection-driver ----
235// RUNG(5): LIFTED VERBATIM into runtime/nx_h2_serve.nx as h2_serve_connection
236// (BL-012 compose-not-duplicate). The daemon calls it per accepted connection in
237// main()'s accept loop -- it is NOT redefined here. Signature:
238// h2_serve_connection(ss_fd, srand, sxpriv, cert_der, cert_len, ed_priv,
239// body, blen) -> i64
240// (0 = clean serve, else the proven honest negative codes; see nx_h2_serve.nx).
241
242func hcd_log(verdict_green: i64, code: i64) -> i64 {
243 let lfd: i64 = sys_openat_append("knowledge/status/h2_nx_h2_serve_curl.log" as *u8, 0x1a4)
244 if lfd < 0 { return 0 }
245 sys_write(lfd, "B1-R4-H2-SERVE-CURL organ=_h2_serve_curl_daemon " as *u8, 48)
246 if verdict_green == 1 {
247 sys_write(lfd, "alpn=h2 status=200 served=GREEN(server-side)\n" as *u8, 45)
248 } else {
249 sys_write(lfd, "verdict=RED code=" as *u8, 17)
250 var v: i64 = code; if v < 0 { sys_write(lfd, "-" as *u8, 1); v = 0 - v }
251 let t: *u8 = sys_mmap(28); var kk: i64 = 0
252 if v == 0 { t[0] = 48 as u8; kk = 1 }
253 while v > 0 { t[kk] = (48 + (v % 10)) as u8; v = v / 10; kk = kk + 1 }
254 while kk > 0 { kk = kk - 1; sys_write(lfd, (((t as i64)+kk) as *u8), 1) }
255 sys_write(lfd, "\n" as *u8, 1)
256 }
257 sys_close(lfd)
258 return 0
259}
260
261func main() -> i64 {
262 sys_write(1, "_h2_serve_curl_daemon: OUR sovereign ALPN-h2 server for EXTERNAL curl interop\n" as *u8, 77)
263
264 // ---- body the server will serve to the external client ----
265 let body: *u8 = sys_mmap(64)
266 let bsrc: *u8 = "hello from nishi h2 server (external interop)" as *u8
267 var blen: i64 = 0
268 while bsrc[blen] != (0 as u8) { body[blen] = bsrc[blen]; blen = blen + 1 }
269
270 // ---- mint the self-signed Ed25519 leaf (SAN=localhost) ----
271 let cert_der: *u8 = sys_mmap(8192)
272 let cert_len: *i64 = sys_mmap(8) as *i64
273 let ed_priv: *u8 = sys_mmap(32)
274 let crc: i64 = hcd_make_cert(cert_der, cert_len, ed_priv)
275 if crc < 0 {
276 sys_write(1, " FAIL cert build\n" as *u8, 18)
277 hcd_log(0, 0 - 90)
278 sys_exit(90); return 90
279 }
280 sys_write(1, " cert minted (SAN=localhost, Ed25519 self-signed)\n" as *u8, 51)
281
282 // ---- export the cert as a PEM curl can trust via --cacert ----
283 let pem_path: *u8 = sys_mmap(64)
284 nx_str_cpy(pem_path, "/tmp/nx_h2_interop_cert.pem" as *u8)
285 let prc: i64 = hcd_write_pem(cert_der, cert_len[0], pem_path)
286 if prc < 0 {
287 sys_write(1, " FAIL pem write\n" as *u8, 17)
288 hcd_log(0, 0 - 91)
289 sys_exit(91); return 91
290 }
291 sys_write(1, " cert.pem written /tmp/nx_h2_interop_cert.pem\n" as *u8, 46)
292
293 // ---- server x25519 + random scratch buffers ----
294 // RUNG(5): the FILL (csprng + x25519 keypair) moves INTO the accept loop so
295 // EACH connection gets FRESH handshake entropy (a server-hello random + an
296 // ephemeral x25519 private reused across connections would be a key-reuse bug);
297 // these buffers are just reusable 32-byte scratch, re-filled per connection.
298 let srand: *u8 = sys_mmap(32)
299 let sxpriv: *u8 = sys_mmap(32)
300
301 // ---- socket / SO_REUSEADDR / bind 127.0.0.1:9443 / listen ----
302 let addr: *u8 = sys_mmap(16)
303 hcd_addr(addr, HCD_PORT_REAL)
304 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
305 if lfd < 0 { hcd_log(0, 0 - 94); sys_exit(94); return 94 }
306 // SO_REUSEADDR = 1 (i32 LE) -- rebind cleanly past TIME_WAIT on re-runs.
307 let optval: *u8 = sys_mmap(4)
308 optval[0] = 1 as u8; optval[1] = 0 as u8; optval[2] = 0 as u8; optval[3] = 0 as u8
309 sys_setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, optval, 4)
310 if sys_bind(lfd, addr, 16) < 0 { hcd_log(0, 0 - 95); sys_exit(95); return 95 }
311 if sys_listen(lfd, 4) < 0 { hcd_log(0, 0 - 96); sys_exit(96); return 96 }
312
313 // Readiness marker -- printed AFTER bind+listen+cert.pem so the orchestrator's
314 // readiness gate (cert.pem-exists + this line) can fire before curl connects.
315 sys_write(1, "BOUND 127.0.0.1:9443\n" as *u8, 21)
316
317 // ---- ACCEPT LOOP (N=HCD_N: serve HCD_N external requests then exit 0) ----
318 // Each curl --http2 GET is a FRESH connection (our GOAWAY closes the prior),
319 // so we accept + serve HCD_N independent h2 connections. Per connection:
320 // regenerate fresh handshake entropy, run the LIFTED h2_serve_connection,
321 // close, stop-on-RED (no fake-green: any non-zero driver code halts honestly).
322 var served: i64 = 0
323 while served < HCD_N {
324 let scfd: i64 = sys_accept(lfd)
325 if scfd < 0 { hcd_log(0, 0 - 97); sys_close(lfd); sys_exit(97); return 97 }
326
327 // FRESH per-connection handshake entropy (server-hello random + ephemeral
328 // x25519 private) -- never reuse across connections.
329 if nx_csprng_fill(srand, 32) != 0 {
330 sys_close(scfd); hcd_log(0, 0 - 92); sys_close(lfd); sys_exit(92); return 92
331 }
332 if x25519_keypair_private(sxpriv) != 0 {
333 sys_close(scfd); hcd_log(0, 0 - 93); sys_close(lfd); sys_exit(93); return 93
334 }
335
336 let sr: i64 = h2_serve_connection(scfd, srand, sxpriv, cert_der, cert_len[0], ed_priv, body, blen)
337 sys_close(scfd)
338
339 if sr != 0 {
340 sys_write(1, " FAIL server side (external interop) code below\n" as *u8, 49)
341 hcd_log(0, sr)
342 sys_close(lfd)
343 sys_exit(0 - sr); return 0 - sr // stop-on-RED, honest, no fake-green
344 }
345
346 served = served + 1
347 sys_write(1, "SERVED 200 #" as *u8, 12)
348 // print served (1-digit for N<=9 is enough; HCD_N=3)
349 let dbuf1: *u8 = sys_mmap(8); dbuf1[0] = ((48 + served) & 0xff) as u8
350 sys_write(1, dbuf1, 1)
351 sys_write(1, "/" as *u8, 1)
352 let dbuf2: *u8 = sys_mmap(8); dbuf2[0] = ((48 + HCD_N) & 0xff) as u8
353 sys_write(1, dbuf2, 1)
354 sys_write(1, ": handshake h2, parsed GET, wrote :status 200\n" as *u8, 46)
355 }
356
357 sys_write(1, "SERVED ALL: " as *u8, 12)
358 let nbuf: *u8 = sys_mmap(8); nbuf[0] = ((48 + HCD_N) & 0xff) as u8
359 sys_write(1, nbuf, 1)
360 sys_write(1, " external h2 connections at :status 200, exiting 0\n" as *u8, 51)
361 hcd_log(1, 0)
362 sys_close(lfd)
363 sys_exit(0); return 0
364}