code wiki / _hdl_build / nx_tls13_app_send_fd.nx
nx_tls13_app_send_fd.nx source
↩ module page · 83 lines · 3729 B
1// nx_tls13_app_send_fd.nx -- chunked TLS 1.3 application-data send to an fd.
2//
3// ROOT CAUSE this fixes: nx_tls13_server_session_app_send encrypts the WHOLE
4// payload as ONE record, but RFC 8446 ยง5.1 caps record plaintext at 2^14
5// (16384) bytes -- compliant clients MUST abort with record_overflow on
6// anything bigger. The hosting path therefore could not serve any asset
7// over ~16.3KB (the /video page is 19KB, its JS ~35KB). This module splits
8// the payload into <=NX_TLS13_SENDFD_CHUNK-byte records and writes each to
9// the fd, handling partial writes. One reusable record buffer, supplied by
10// the caller (constant-memory daemons hoist it once).
11//
12// Composes nx_tls13_server_session_app_send (KAT'd record build) -- the
13// per-record path is UNCHANGED, so existing small-response behaviour is
14// byte-identical (API contract stability: additive, nothing renamed).
15//
16// Gate: nx_tls13_app_send_fd_gate.nx (fabricated CONNECTED session with
17// fixed keys -> 40KB payload -> records to a file -> decrypt each record
18// with nx_tls13_record_decrypt_v2 -> reassembled plaintext byte-compares).
19// license_tier: ORIGINAL
20
21import "nx_syscalls.nx"
22import "nx_tls13.nx"
23import "nx_tls13_record.nx"
24import "nx_tls13_server_session.nx"
25import "nx_tls13_server_session_app_data.nx"
26
27// Per-record plaintext chunk. Below the 16384 RFC cap with margin so
28// header(5) + inner-type(1) + tag(16) stays well inside a 16384+384
29// record buffer.
30const NX_TLS13_SENDFD_CHUNK: i64 = 16000
31// Minimum record buffer the caller must supply: chunk + header + type + tag.
32const NX_TLS13_SENDFD_REC_MIN: i64 = 16384
33
34// write_all: loop sys_write until n bytes are on the fd (partial writes
35// happen on sockets under pressure). Returns n, or negative on error.
36func nx_tls13_sendfd_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
37 var off: i64 = 0
38 while off < n {
39 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off)
40 if w <= 0 { return 0 - 1 }
41 off = off + w
42 }
43 return n
44}
45
46// Encrypt payload as a sequence of <=CHUNK-byte application-data records,
47// writing each record to fd as it is built. rec_buf/rec_cap is scratch for
48// ONE wire record (rec_cap >= NX_TLS13_SENDFD_REC_MIN). Returns payload_len
49// on success, negative on any session/encrypt/write error.
50func nx_tls13_app_send_fd(
51 session: *Tls13ServerSession,
52 payload: *u8, payload_len: i64,
53 fd: i64,
54 rec_buf: *u8, rec_cap: i64
55) -> i64 {
56 if (session as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
57 if (payload as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
58 if payload_len < 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
59 if rec_cap < NX_TLS13_SENDFD_REC_MIN { return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW }
60
61 // Zero-length payload: still emit one empty record (callers that send
62 // "" expect a record on the wire, matching app_send's behaviour).
63 if payload_len == 0 {
64 let w0: i64 = nx_tls13_server_session_app_send(session, payload, 0, rec_buf, rec_cap)
65 if w0 <= 0 { return w0 }
66 let wr0: i64 = nx_tls13_sendfd_write_all(fd, rec_buf, w0)
67 if wr0 < 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
68 return 0
69 }
70
71 var off: i64 = 0
72 while off < payload_len {
73 var chunk: i64 = payload_len - off
74 if chunk > NX_TLS13_SENDFD_CHUNK { chunk = NX_TLS13_SENDFD_CHUNK }
75 let w: i64 = nx_tls13_server_session_app_send(
76 session, (payload as i64 + off) as *u8, chunk, rec_buf, rec_cap)
77 if w <= 0 { return w }
78 let wr: i64 = nx_tls13_sendfd_write_all(fd, rec_buf, w)
79 if wr < 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
80 off = off + chunk
81 }
82 return payload_len
83}