code wiki / _hdl_build / nx_tls13_app_send_fd.nx
nx_tls13_app_send_fd.nx source
↩ module page · 164 lines · 7107 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_fio.nx"
23
24struct NxTlsFileSendResult {
25 stage: *u8,
26 code: i64,
27 header_bytes: i64,
28 body_bytes: i64,
29 read_bytes: i64,
30 close_code: i64,
31 write_code: i64,
32 wire_bytes: i64
33}
34
35
36import "nx_tls13.nx"
37import "nx_tls13_record.nx"
38import "nx_tls13_server_session.nx"
39import "nx_tls13_server_session_app_data.nx"
40
41// Per-record plaintext chunk. Below the 16384 RFC cap with margin so
42// header(5) + inner-type(1) + tag(16) stays well inside a 16384+384
43// record buffer.
44const NX_TLS13_SENDFD_CHUNK: i64 = 16000
45// Minimum record buffer the caller must supply: chunk + header + type + tag.
46const NX_TLS13_SENDFD_REC_MIN: i64 = 16384
47
48// write_all: loop sys_write until n bytes are on the fd (partial writes
49// happen on sockets under pressure). Returns n, or negative on error.
50func nx_tls13_sendfd_write_all_receipt(fd: i64, buf: *u8, n: i64, result: *NxTlsFileSendResult) -> i64 {
51 var off: i64=0
52 while off<n {
53 let w: i64=sys_write(fd,buf+off,n-off)
54 if w==FIO_EINTR { continue }
55 if w<=0 {
56 var code: i64=w
57 if code==0 { code=FIO_EIO }
58 if (result as i64)!=0 { result.write_code=code }
59 return code
60 }
61 off=off+w
62 if (result as i64)!=0 { result.wire_bytes=result.wire_bytes+w }
63 }
64 return n
65}
66func nx_tls13_sendfd_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
67 let rc: i64=nx_tls13_sendfd_write_all_receipt(fd,buf,n,0 as *NxTlsFileSendResult)
68 if rc<0 { return 0-1 }
69 return rc
70}
71
72// Encrypt payload as a sequence of <=CHUNK-byte application-data records,
73// writing each record to fd as it is built. rec_buf/rec_cap is scratch for
74// ONE wire record (rec_cap >= NX_TLS13_SENDFD_REC_MIN). Returns payload_len
75// on success, negative on any session/encrypt/write error.
76func nx_tls13_app_send_fd_receipt(
77 session: *Tls13ServerSession,
78 payload: *u8, payload_len: i64,
79 fd: i64,
80 rec_buf: *u8, rec_cap: i64, result: *NxTlsFileSendResult
81) -> i64 {
82 if (session as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
83 if (payload as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
84 if payload_len < 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
85 if rec_cap < NX_TLS13_SENDFD_REC_MIN { return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW }
86
87 // Zero-length payload: still emit one empty record (callers that send
88 // "" expect a record on the wire, matching app_send's behaviour).
89 if payload_len == 0 {
90 let w0: i64 = nx_tls13_server_session_app_send(session, payload, 0, rec_buf, rec_cap)
91 if w0 <= 0 { return w0 }
92 let wr0: i64 = nx_tls13_sendfd_write_all_receipt(fd, rec_buf, w0,result)
93 if wr0 < 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
94 return 0
95 }
96
97 var off: i64 = 0
98 while off < payload_len {
99 var chunk: i64 = payload_len - off
100 if chunk > NX_TLS13_SENDFD_CHUNK { chunk = NX_TLS13_SENDFD_CHUNK }
101 let w: i64 = nx_tls13_server_session_app_send(
102 session, (payload as i64 + off) as *u8, chunk, rec_buf, rec_cap)
103 if w <= 0 { return w }
104 let wr: i64 = nx_tls13_sendfd_write_all_receipt(fd, rec_buf, w,result)
105 if wr < 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
106 off = off + chunk
107 }
108 return payload_len
109}
110
111
112// Counts completed plaintext sends, not remote acknowledgement. A failed send
113// may have written a prefix; the connection must close and must not replay it.
114
115func nx_tls13_app_send_fd(session: *Tls13ServerSession,payload: *u8,payload_len: i64,fd: i64,rec_buf: *u8,rec_cap: i64) -> i64 {
116 return nx_tls13_app_send_fd_receipt(session,payload,payload_len,fd,rec_buf,rec_cap,0 as *NxTlsFileSendResult)
117}
118
119// Consumes the initialized reader's descriptor on every return. Scratch may
120// alias the header because the entire header is sent before the first read.
121func nx_tls13_file_send_fd(
122 session: *Tls13ServerSession, reader: *NxFileReadRegion,
123 header: *u8, header_len: i64, scratch: *u8, scratch_cap: i64,
124 fd: i64, rec_buf: *u8, rec_cap: i64, result: *NxTlsFileSendResult
125) -> i64 {
126 result.stage="input";result.code=0;result.header_bytes=0
127 result.body_bytes=0;result.read_bytes=0;result.close_code=0;result.write_code=0;result.wire_bytes=0
128 if (reader as i64)==0 { result.code=FIO_EINVAL;return result.code }
129 if scratch_cap<=0 || (scratch as i64)==0 || header_len<=0 {
130 result.code=FIO_EINVAL
131 } else {
132 if reader.code!=0 { result.stage=reader.stage;result.code=reader.code }
133 else {
134 result.stage="header-send"
135 let h: i64=nx_tls13_app_send_fd_receipt(session,header,header_len,fd,rec_buf,rec_cap,result)
136 if h!=header_len { result.code=h;if h>=0 { result.code=FIO_EIO } }
137 else { result.header_bytes=h }
138 }
139 }
140 // One transport-record-sized read bounds file I/O even if the caller's
141 // assembled response buffer is much larger.
142 var cap: i64=scratch_cap
143 if cap>NX_TLS13_SENDFD_CHUNK { cap=NX_TLS13_SENDFD_CHUNK }
144 while result.code==0 && result.body_bytes<reader.length {
145 result.stage="body-read"
146 let got: i64=fio_region_next(reader,scratch,cap)
147 result.read_bytes=reader.read_bytes
148 if got<=0 {
149 result.code=got
150 if got==0 { result.code=FIO_EIO }
151 result.stage=reader.stage
152 break
153 }
154 result.stage="body-send"
155 let sent: i64=nx_tls13_app_send_fd_receipt(session,scratch,got,fd,rec_buf,rec_cap,result)
156 if sent!=got { result.code=sent;if sent>=0 { result.code=FIO_EIO };break }
157 result.body_bytes=result.body_bytes+sent
158 }
159 let closed: i64=fio_region_close(reader)
160 result.read_bytes=reader.read_bytes;result.close_code=reader.close_code
161 if result.code==0 && closed<0 { result.stage=reader.stage;result.code=closed }
162 if result.code==0 { result.stage="complete" }
163 return result.code
164}