code wiki / (root) / nx_tls13_read_record_from_fd.nx

nx_tls13_read_record_from_fd.nx source

↩ module page · 128 lines · 4941 B

1// nx_tls13_read_record_from_fd.nx -- read ONE TLS record from a 2// file descriptor into a caller-supplied buffer. 3// 4// Step 3c.6a of the nx_https_client wiring arc. This is the 5// network-IO helper that step 3c.6b (the top-level handshake-run 6// orchestrator) uses every time it needs the server's next 7// record. 8// 9// TLS record framing (RFC 8446 §5.1): 10// 11// [0] ContentType (1 byte) 12// [1..2] legacy_record_version (2 bytes, big-endian) 13// [3..4] length (2 bytes, big-endian) 14// [5..] fragment / ciphertext (length bytes) 15// 16// Total record size = 5 + length. 17// 18// This primitive: 19// 1. Reads 5 bytes from fd (looping sys_read to handle partial 20// reads from TCP -- a typical kernel ships TCP data in 21// MTU-sized chunks) 22// 2. Parses the 2-byte length field 23// 3. Validates length <= out_cap - 5 and length <= TLS spec max 24// (2^14 + 256 = 16640 per RFC 8446 §5.2) 25// 4. Reads `length` more bytes from fd into out_buf[5..5+length] 26// 5. Returns 5 + length on success 27// 28// On failure: returns NEGATIVE -NX_TLS13_READ_REC_* code. 29// 30// Public API: 31// nx_tls13_read_record_from_fd(fd, out_buf, out_cap) 32// -> POSITIVE total bytes written | NEGATIVE -verdict 33// nx_tls13_read_rec_verdict_is_valid(v) -> 0|1 34// 35// Sealed verdict: 36// NX_TLS13_READ_REC_OK positive rc = bytes written 37// NX_TLS13_READ_REC_EOF fd closed before 5-byte header 38// NX_TLS13_READ_REC_PAYLOAD_EOF fd closed mid-payload 39// NX_TLS13_READ_REC_BUF_OVERFLOW length exceeds out_cap 40// NX_TLS13_READ_REC_TOO_LONG length exceeds RFC §5.2 max 41// NX_TLS13_READ_REC_READ_ERR sys_read returned negative 42// 43// Per Cardinals 9 (single-responsibility -- read ONE record), 44// 12 (defensive at boundaries -- bounds-check both directions of 45// the length field), 22 (composition -- this + nx_tls13_record_decrypt 46// + the session state machine compose into the orchestrator), 23 47// (preamble names the partial-read handling discipline). 48// 49// license_tier: INDEPENDENT_REDERIVE 50// genealogy_id: international-research-sources/ietf/rfc_8446 + posix/sys_read 51// lineage_id: nishi_tls13_read_record_from_fd_q10 52 53// nx_safety_envelope: 54// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 55// sil_target: SIL1 56// evidence: [bulk_applied_2026-05-19, tls13-read-record-from-fd] 57// verdict: NOT_YET_EVALUATED 58 59import "nx_syscalls.nx" 60import "nx_tls13_record.nx" 61 62const NX_TLS13_READ_REC_OK: i64 = 1 63const NX_TLS13_READ_REC_EOF: i64 = 2 64const NX_TLS13_READ_REC_PAYLOAD_EOF: i64 = 3 65const NX_TLS13_READ_REC_BUF_OVERFLOW: i64 = 4 66const NX_TLS13_READ_REC_TOO_LONG: i64 = 5 67const NX_TLS13_READ_REC_READ_ERR: i64 = 6 68const NX_TLS13_READ_REC_VERDICT_N: i64 = 7 69 70func nx_tls13_read_rec_verdict_is_valid(v: i64) -> i64 { 71 if v < NX_TLS13_READ_REC_OK { return 0 } 72 if v >= NX_TLS13_READ_REC_VERDICT_N { return 0 } 73 return 1 74} 75 76// Read exactly `n` bytes from fd into buf at offset `off`. Loops 77// sys_read to handle TCP partial-read behavior. Returns 0 on 78// success, -1 on EOF, -2 on sys_read negative. 79func _read_n(fd: i64, buf: *u8, off: i64, n: i64) -> i64 { 80 var got: i64 = 0 81 while got < n { 82 let r: i64 = sys_read(fd, (buf as i64 + off + got) as *u8, n - got) 83 if r == 0 { return 0 - 1 } // EOF before n bytes 84 if r < 0 { return 0 - 2 } // sys_read error 85 got = got + r 86 } 87 return 0 88} 89 90// Read one TLS record (header + payload) from fd into out_buf. 91// 92// Returns POSITIVE 5 + payload_len on success. 93// Returns NEGATIVE -NX_TLS13_READ_REC_* code on failure. 94func nx_tls13_read_record_from_fd( 95 fd: i64, out_buf: *u8, out_cap: i64 96) -> i64 { 97 if out_cap < NX_TLS13_RECORD_HEADER_LEN { 98 return 0 - NX_TLS13_READ_REC_BUF_OVERFLOW 99 } 100 101 // ---- Phase 1: read 5-byte header ---- 102 let hr: i64 = _read_n(fd, out_buf, 0, NX_TLS13_RECORD_HEADER_LEN) 103 if hr == 0 - 1 { return 0 - NX_TLS13_READ_REC_EOF } 104 if hr == 0 - 2 { return 0 - NX_TLS13_READ_REC_READ_ERR } 105 106 // ---- Phase 2: parse length ---- 107 let length: i64 = ((out_buf[3] & 0xff) << 8) | (out_buf[4] & 0xff) 108 if length < 0 { return 0 - NX_TLS13_READ_REC_TOO_LONG } 109 if length > NX_TLS13_MAX_RECORD_PAYLOAD { 110 return 0 - NX_TLS13_READ_REC_TOO_LONG 111 } 112 if NX_TLS13_RECORD_HEADER_LEN + length > out_cap { 113 return 0 - NX_TLS13_READ_REC_BUF_OVERFLOW 114 } 115 116 // ---- Phase 3: read payload ---- 117 let pr: i64 = _read_n(fd, out_buf, NX_TLS13_RECORD_HEADER_LEN, length) 118 if pr == 0 - 1 { return 0 - NX_TLS13_READ_REC_PAYLOAD_EOF } 119 if pr == 0 - 2 { return 0 - NX_TLS13_READ_REC_READ_ERR } 120 121 return NX_TLS13_RECORD_HEADER_LEN + length 122} 123 124// Compile-only smoke. Real KAT in 125// nx_tls13_read_record_from_fd_test.nx. 126func main() -> i64 { 127 return 0 128}