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}