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}