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}