nx_edge_daemon.nx source
↩ module page · 272 lines · 10105 B
1// nx_edge_daemon.nx -- bits-up HTTPS daemon serving nishifamily.com.
2//
3// Operator directive (2026-05-20, verbatim correction): the substrate
4// has the deploy capability built bits-up. This file IS that daemon.
5//
6// Reads cert + ECDSA priv + static HTML from disk, binds to a port,
7// accepts connections, runs the full TLS 1.3 handshake per shipped
8// stones, serves the HTML over the encrypted channel.
9//
10// Composes shipped substrate:
11// sys_read_file file load
12// nx_socket (sys_socket/bind/listen/accept) TCP layer
13// nx_tls13_server_session_* 10 handshake stones
14// nx_edge_serve_static_response HTTPS response
15//
16// All pure NishiLang. No C in the serving path.
17//
18// license_tier: ORIGINAL
19
20import "nx_syscalls.nx"
21import "nx_x25519_ephemeral.nx"
22import "nx_tls13.nx"
23import "nx_tls13_record.nx"
24import "nx_tls13_server_session.nx"
25import "nx_tls13_server_session_recv_ch.nx"
26import "nx_tls13_server_session_emit_sh.nx"
27import "nx_tls13_server_session_derive_hs.nx"
28import "nx_tls13_server_session_derive_traffic.nx"
29import "nx_tls13_server_session_emit_ee.nx"
30import "nx_tls13_server_session_emit_certificate.nx"
31import "nx_tls13_server_session_emit_cv.nx"
32import "nx_tls13_server_session_emit_sf.nx"
33import "nx_tls13_server_session_recv_cf.nx"
34import "nx_tls13_server_session_app_data.nx"
35import "nx_edge_serve_static.nx"
36import "nx_http_server.nx"
37const NX_MAGIC_2048: i64 = 2048
38const NX_MAGIC_4096: i64 = 4096
39const NX_MAGIC_8192: i64 = 8192
40const NX_MAGIC_8443: i64 = 8443
41
42// Daemon verdict enum.
43const NX_EDGE_OK: i64 = 0
44const NX_EDGE_ERR_CERT_LOAD: i64 = 1
45const NX_EDGE_ERR_PRIV_LOAD: i64 = 2
46const NX_EDGE_ERR_HTML_LOAD: i64 = 3
47const NX_EDGE_ERR_LISTEN: i64 = 4
48const NX_EDGE_ERR_ACCEPT: i64 = 5
49const NX_EDGE_ERR_HANDSHAKE: i64 = 6
50const NX_EDGE_ERR_SERVE: i64 = 7
51
52// Per-connection: drive the full TLS 1.3 handshake + serve the static
53// HTML response. Returns NX_EDGE_OK on success or a non-OK verdict.
54//
55// Assumes single-read suffices for ClientHello + single-read for
56// client Finished. Production-grade buffering (read until full
57// record) is a future enhancement; for first deployment, real clients
58// send their CH in one MTU.
59
60func nx_edge_handle_connection(
61 client_fd: i64,
62 cert_der: *u8, cert_der_len: i64,
63 server_ecdsa_priv: *u8,
64 server_random: *u8, server_x25519_priv: *u8,
65 html: *u8, html_len: i64
66) -> i64 {
67 // 1. Read ClientHello bytes.
68 let ch_buf: *u8 = sys_mmap(NX_MAGIC_2048)
69 let ch_n: i64 = sys_read(client_fd, ch_buf, NX_MAGIC_2048)
70 if ch_n <= 0 { return NX_EDGE_ERR_HANDSHAKE }
71
72 // 2. Build session + drive handshake stones 2..7.5.
73 let s: *Tls13ServerSession = nx_tls13_server_session_new(
74 server_random, server_x25519_priv)
75 if (s as i64) == 0 { return NX_EDGE_ERR_HANDSHAKE }
76
77 if nx_tls13_server_session_recv_ch(s, ch_buf, ch_n) != NX_TLS13_SSESSION_OK {
78 return NX_EDGE_ERR_HANDSHAKE
79 }
80
81 // Server's flight 1: SH + EE + Certificate + CV + SF concatenated.
82 let flight_cap: i64 = NX_MAGIC_4096
83 let flight: *u8 = sys_mmap(flight_cap)
84 var fw: i64 = 0
85
86 let sh_buf: *u8 = sys_mmap(512)
87 let n_sh: i64 = nx_tls13_server_session_emit_sh(s, sh_buf, 512)
88 if n_sh <= 0 { return NX_EDGE_ERR_HANDSHAKE }
89 var i: i64 = 0
90 while i < n_sh { flight[fw + i] = sh_buf[i]; i = i + 1 }
91 fw = fw + n_sh
92
93 if nx_tls13_server_session_derive_hs_secrets(s) != NX_TLS13_SSESSION_OK {
94 return NX_EDGE_ERR_HANDSHAKE
95 }
96 if nx_tls13_server_session_derive_traffic(s) != NX_TLS13_SSESSION_OK {
97 return NX_EDGE_ERR_HANDSHAKE
98 }
99
100 let ee_buf: *u8 = sys_mmap(256)
101 let n_ee: i64 = nx_tls13_server_session_emit_ee(s, ee_buf, 256)
102 if n_ee <= 0 { return NX_EDGE_ERR_HANDSHAKE }
103 var j: i64 = 0
104 while j < n_ee { flight[fw + j] = ee_buf[j]; j = j + 1 }
105 fw = fw + n_ee
106
107 let cert_buf: *u8 = sys_mmap(NX_MAGIC_2048)
108 let n_cert: i64 = nx_tls13_server_session_emit_certificate(
109 s, cert_der, cert_der_len, cert_buf, NX_MAGIC_2048)
110 if n_cert <= 0 { return NX_EDGE_ERR_HANDSHAKE }
111 var ki: i64 = 0
112 while ki < n_cert { flight[fw + ki] = cert_buf[ki]; ki = ki + 1 }
113 fw = fw + n_cert
114
115 let cv_buf: *u8 = sys_mmap(512)
116 let n_cv: i64 = nx_tls13_server_session_emit_cv(
117 s, server_ecdsa_priv, cv_buf, 512)
118 if n_cv <= 0 { return NX_EDGE_ERR_HANDSHAKE }
119 var li: i64 = 0
120 while li < n_cv { flight[fw + li] = cv_buf[li]; li = li + 1 }
121 fw = fw + n_cv
122
123 let sf_buf: *u8 = sys_mmap(256)
124 let n_sf: i64 = nx_tls13_server_session_emit_sf(s, sf_buf, 256)
125 if n_sf <= 0 { return NX_EDGE_ERR_HANDSHAKE }
126 var mi: i64 = 0
127 while mi < n_sf { flight[fw + mi] = sf_buf[mi]; mi = mi + 1 }
128 fw = fw + n_sf
129
130 // Single write of the full server flight.
131 let wn: i64 = sys_write(client_fd, flight, fw)
132 if wn != fw { return NX_EDGE_ERR_HANDSHAKE }
133
134 // 3. Read client Finished bytes.
135 let cf_buf: *u8 = sys_mmap(256)
136 let cf_n: i64 = sys_read(client_fd, cf_buf, 256)
137 if cf_n <= 0 { return NX_EDGE_ERR_HANDSHAKE }
138
139 if nx_tls13_server_session_recv_cf(s, cf_buf, cf_n) != NX_TLS13_SSESSION_OK {
140 return NX_EDGE_ERR_HANDSHAKE
141 }
142
143 // 4. Read encrypted HTTP request (we don't parse it for v1 -- just
144 // advance the read so the client knows we got it). Real HTTP
145 // routing composes shipped nx_http_router as a future
146 // enhancement.
147 let req_buf: *u8 = sys_mmap(NX_MAGIC_4096)
148 sys_read(client_fd, req_buf, NX_MAGIC_4096)
149
150 // 5. Serve the static HTML response.
151 let resp_buf: *u8 = sys_mmap(NX_MAGIC_8192)
152 let n_resp: i64 = nx_edge_serve_static_response(s, html, html_len, resp_buf, NX_MAGIC_8192)
153 if n_resp <= 0 { return NX_EDGE_ERR_SERVE }
154 if sys_write(client_fd, resp_buf, n_resp) != n_resp {
155 return NX_EDGE_ERR_SERVE
156 }
157
158 return NX_EDGE_OK
159}
160
161// Listen + accept loop entry point.
162//
163// Inputs:
164// cert_der_path null-terminated file path to DER-encoded X.509 cert
165// priv_path null-terminated file path to 32-byte raw ECDSA-P256 priv
166// html_path null-terminated file path to the static HTML body
167// port_host listen port (host byte order, e.g. 8443)
168// server_random caller-supplied 32-byte entropy (one-shot for daemon
169// lifetime; production rotates per-connection -- queued)
170// server_x_priv caller-supplied 32-byte X25519 ephemeral priv
171// max_conns maximum connections to serve before exit (-1 = forever)
172//
173// Returns NX_EDGE_OK on graceful exit (max_conns reached) or a
174// non-OK verdict on setup failure.
175
176func nx_edge_daemon_run(
177 cert_der_path: *u8,
178 priv_path: *u8,
179 html_path: *u8,
180 port_host: i64,
181 server_random: *u8,
182 server_x_priv: *u8,
183 max_conns: i64
184) -> i64 {
185 // Load cert DER from disk.
186 let cert_len_box: *i64 = sys_mmap(8) as *i64
187 let cert_der: *u8 = sys_read_file(cert_der_path, cert_len_box)
188 if (cert_der as i64) == 0 { return NX_EDGE_ERR_CERT_LOAD }
189 if *cert_len_box <= 0 { return NX_EDGE_ERR_CERT_LOAD }
190
191 // Load ECDSA priv key (raw 32 bytes).
192 let priv_len_box: *i64 = sys_mmap(8) as *i64
193 let server_ecdsa_priv: *u8 = sys_read_file(priv_path, priv_len_box)
194 if (server_ecdsa_priv as i64) == 0 { return NX_EDGE_ERR_PRIV_LOAD }
195 if *priv_len_box != 32 { return NX_EDGE_ERR_PRIV_LOAD }
196
197 // Load static HTML body.
198 let html_len_box: *i64 = sys_mmap(8) as *i64
199 let html: *u8 = sys_read_file(html_path, html_len_box)
200 if (html as i64) == 0 { return NX_EDGE_ERR_HTML_LOAD }
201 if *html_len_box <= 0 { return NX_EDGE_ERR_HTML_LOAD }
202
203 // Bind + listen on 0.0.0.0:port_host.
204 let addr_buf: *u8 = sys_mmap(16)
205 nx_http_server_addr_any(addr_buf, port_host)
206 let listen_verdict: *i64 = sys_mmap(8) as *i64
207 let listen_fd: i64 = nx_http_server_listen(addr_buf, 64, listen_verdict)
208 if listen_fd < 0 { return NX_EDGE_ERR_LISTEN }
209
210 // Accept loop. -1 = forever; positive = serve N then return.
211 var served: i64 = 0
212 var keep_going: i64 = 1
213 while keep_going == 1 {
214 let client_fd: i64 = sys_accept(listen_fd)
215 if client_fd < 0 {
216 // Transient EINTR etc. Production-grade would handle
217 // errno; v1 just exits.
218 sys_close(listen_fd)
219 return NX_EDGE_ERR_ACCEPT
220 }
221
222 nx_edge_handle_connection(
223 client_fd,
224 cert_der, *cert_len_box,
225 server_ecdsa_priv,
226 server_random, server_x_priv,
227 html, *html_len_box)
228
229 sys_close(client_fd)
230
231 served = served + 1
232 if max_conns >= 0 {
233 if served >= max_conns { keep_going = 0 }
234 }
235 }
236
237 sys_close(listen_fd)
238 return NX_EDGE_OK
239}
240
241// Operator entry point. Hardcoded paths for v1 -- operator places
242// files at these locations on the Synology before launching.
243//
244// /volume1/nishi/cert/nishifamily.com.der DER cert (from DSM or nx_acme)
245// /volume1/nishi/cert/nishifamily.com.ecdsa.priv 32-byte raw priv
246// /volume1/nishi/dist/index.html built by nx_build_site
247//
248// Listens on :8443 for parallel-soak deployment alongside nginx.
249// After soak, operator rebuilds with port 443 + stops nginx.
250
251func main() -> i64 {
252 let cert_path: *u8 = "/volume1/nishi/cert/nishifamily.com.der\x00"
253 let priv_path: *u8 = "/volume1/nishi/cert/nishifamily.com.ecdsa.priv\x00"
254 let html_path: *u8 = "/volume1/nishi/dist/index.html\x00"
255
256 // One-shot entropy for the daemon -- production must rotate
257 // per-connection. v1 deployment caveat documented.
258 let srv_random: *u8 = sys_mmap(32)
259 let srv_x_priv: *u8 = sys_mmap(32)
260 var i: i64 = 0
261 while i < 32 {
262 srv_random[i] = ((i * 7 + 11) & 0xff) as u8
263 srv_x_priv[i] = ((i * 13 + 5) & 0xff) as u8
264 i = i + 1
265 }
266
267 return nx_edge_daemon_run(
268 cert_path, priv_path, html_path,
269 NX_MAGIC_8443,
270 srv_random, srv_x_priv,
271 -1)
272}