code wiki / _hdl_build / nx_tor_cell.nx
nx_tor_cell.nx source
↩ module page · 172 lines · 8791 B
1// nx_tor_cell.nx -- SOVEREIGN Tor link-protocol CELL codec (tor-spec.txt sec.3 + sec.4 handshake cells).
2// Phase 2 of the anonymizing-transport arc: the byte framing that carries the Tor link handshake
3// (VERSIONS / CERTS / AUTH_CHALLENGE / NETINFO) over the ALREADY-BUILT sovereign TLS 1.3 client
4// (nx_tls13_client_session_run gives the encrypted app-data channel; these cells are its plaintext).
5//
6// Cell shapes (tor-spec sec.3):
7// FIXED-length cell: CircID[CIRCID_LEN] | Command[1] | Payload[509] (512B if CircID=2 [link v<=3],
8// 514B if CircID=4 [link v4+])
9// VARIABLE-length cell: CircID[CIRCID_LEN] | Command[1] | Length[2] | Payload[Length]
10// VERSIONS (cmd 7) is variable-length and ALWAYS uses a 2-byte CircID (sent before version negotiation).
11//
12// HONEST SCOPE: this is the CELL FRAMING (encode/decode), gated byte-exact vs the spec layout. It is NOT a
13// live Tor connection (no relay reachable; zero external calls). anon_is_anonymizing() stays 0. Sovereign:
14// nx_syscalls only (pure byte codec). license_tier: ORIGINAL
15import "nx_syscalls.nx"
16
17// ---- cell commands (tor-spec sec.3/sec.5) ----
18const TCELL_PADDING: i64 = 0
19const TCELL_CREATE: i64 = 1
20const TCELL_CREATED: i64 = 2
21const TCELL_RELAY: i64 = 3
22const TCELL_DESTROY: i64 = 4
23const TCELL_CREATE_FAST: i64 = 5
24const TCELL_CREATED_FAST: i64 = 6
25const TCELL_VERSIONS: i64 = 7 // variable-length; CircID always 2 bytes
26const TCELL_NETINFO: i64 = 8 // fixed-length
27const TCELL_RELAY_EARLY: i64 = 9
28const TCELL_CREATE2: i64 = 10 // ntor onion skin rides here (Phase 3)
29const TCELL_CREATED2: i64 = 11
30const TCELL_VPADDING: i64 = 128 // variable-length
31const TCELL_CERTS: i64 = 129 // variable-length
32const TCELL_AUTH_CHALLENGE: i64 = 130 // variable-length
33const TCELL_AUTHENTICATE: i64 = 131 // variable-length
34
35const TCELL_PAYLOAD_LEN: i64 = 509 // fixed-cell payload is always 509 bytes (tor-spec sec.3)
36const TCELL_NETINFO_ATYPE_IPV4: i64 = 4
37const TCELL_NETINFO_ATYPE_IPV6: i64 = 6
38
39const TCELL_E_SHORT: i64 = 0 - 1 // buffer shorter than the declared/required length
40const TCELL_E_CMD: i64 = 0 - 2 // unexpected command
41const TCELL_E_FORMAT: i64 = 0 - 3 // malformed field (odd length, overrun, ...)
42
43// ---- big-endian field helpers ----
44func tc_put_u16(buf: *u8, off: i64, v: i64) -> i64 { buf[off]=((v>>8)&0xff) as u8; buf[off+1]=(v&0xff) as u8; return off+2 }
45func tc_put_u32(buf: *u8, off: i64, v: i64) -> i64 { buf[off]=((v>>24)&0xff) as u8; buf[off+1]=((v>>16)&0xff) as u8; buf[off+2]=((v>>8)&0xff) as u8; buf[off+3]=(v&0xff) as u8; return off+4 }
46func tc_get_u16(buf: *u8, off: i64) -> i64 { return ((buf[off] as i64)<<8) | (buf[off+1] as i64) }
47func tc_get_u32(buf: *u8, off: i64) -> i64 { return ((buf[off] as i64)<<24)|((buf[off+1] as i64)<<16)|((buf[off+2] as i64)<<8)|(buf[off+3] as i64) }
48
49// circid header length for a negotiated link version: 4 for v4+, else 2 (tor-spec sec.3).
50func tc_circid_len(link_version: i64) -> i64 { if link_version >= 4 { return 4 } return 2 }
51
52// ---- generic framing ----
53
54// FIXED-length cell: CircID | Command | Payload(paylen, zero-padded to 509). Returns total cell length.
55func tc_build_fixed(circid: i64, cmd: i64, payload: *u8, paylen: i64, circid_len: i64, out: *u8) -> i64 {
56 var o: i64 = 0
57 if circid_len == 4 { o = tc_put_u32(out, o, circid) } else { o = tc_put_u16(out, o, circid) }
58 out[o] = cmd as u8; o = o + 1
59 var i: i64 = 0
60 while i < paylen { out[o+i] = payload[i]; i = i + 1 }
61 while i < TCELL_PAYLOAD_LEN { out[o+i] = 0 as u8; i = i + 1 }
62 return o + TCELL_PAYLOAD_LEN
63}
64
65// VARIABLE-length cell: CircID | Command | Length[2] | Payload[Length]. Returns total cell length.
66func tc_build_var(circid: i64, cmd: i64, payload: *u8, paylen: i64, circid_len: i64, out: *u8) -> i64 {
67 var o: i64 = 0
68 if circid_len == 4 { o = tc_put_u32(out, o, circid) } else { o = tc_put_u16(out, o, circid) }
69 out[o] = cmd as u8; o = o + 1
70 o = tc_put_u16(out, o, paylen)
71 var i: i64 = 0
72 while i < paylen { out[o+i] = payload[i]; i = i + 1 }
73 return o + paylen
74}
75
76// parse a VARIABLE-length cell header; writes cmd + payload offset + payload length via out params.
77// Returns total cell length (header+payload) on success, negative on truncation.
78func tc_parse_var(buf: *u8, buflen: i64, circid_len: i64, out_cmd: *i64, out_ploff: *i64, out_plen: *i64) -> i64 {
79 let hdr: i64 = circid_len + 3 // CircID + CMD + Length
80 if buflen < hdr { return TCELL_E_SHORT }
81 out_cmd[0] = buf[circid_len] as i64
82 let plen: i64 = tc_get_u16(buf, circid_len + 1)
83 if buflen < hdr + plen { return TCELL_E_SHORT }
84 out_ploff[0] = hdr
85 out_plen[0] = plen
86 return hdr + plen
87}
88
89// ---- VERSIONS (cmd 7) ----
90// Build a VERSIONS cell offering `vers[0..n)`. CircID = 2 bytes (always, pre-negotiation). Returns length.
91func tc_build_versions(vers: *i64, n: i64, out: *u8) -> i64 {
92 let pl: *u8 = sys_mmap(256); var po: i64 = 0
93 var i: i64 = 0
94 while i < n { po = tc_put_u16(pl, po, vers[i]); i = i + 1 }
95 return tc_build_var(0, TCELL_VERSIONS, pl, po, 2, out)
96}
97
98// Parse a VERSIONS cell (2-byte CircID). Extracts up to `max` versions into out_vers. Returns count, or neg.
99func tc_parse_versions(buf: *u8, buflen: i64, out_vers: *i64, max: i64) -> i64 {
100 if buflen < 5 { return TCELL_E_SHORT }
101 if (buf[2] as i64) != TCELL_VERSIONS { return TCELL_E_CMD }
102 let plen: i64 = tc_get_u16(buf, 3)
103 if buflen < 5 + plen { return TCELL_E_SHORT }
104 if (plen % 2) != 0 { return TCELL_E_FORMAT }
105 var n: i64 = plen / 2
106 var i: i64 = 0
107 while i < n { if i < max { out_vers[i] = tc_get_u16(buf, 5 + i*2) } i = i + 1 }
108 return n
109}
110
111// Pick the highest common link version between two version lists (0 if none).
112func tc_negotiate_version(a: *i64, na: i64, b: *i64, nb: i64) -> i64 {
113 var best: i64 = 0; var i: i64 = 0
114 while i < na { var j: i64 = 0; while j < nb { if a[i]==b[j] { if a[i]>best { best=a[i] } } j=j+1 } i=i+1 }
115 return best
116}
117
118// ---- NETINFO (cmd 8, fixed) ----
119// Client form: Timestamp[4] | OtherAddr(type=4,len=4,ip) | MyAddrCount=0. Padded to 509. Returns cell length.
120func tc_build_netinfo(circid_len: i64, timestamp: i64, other_ip4: *u8, out: *u8) -> i64 {
121 let pl: *u8 = sys_mmap(64); var po: i64 = 0
122 po = tc_put_u32(pl, po, timestamp)
123 pl[po] = TCELL_NETINFO_ATYPE_IPV4 as u8; po = po + 1 // OtherAddr type = IPv4
124 pl[po] = 4 as u8; po = po + 1 // OtherAddr length = 4
125 var i: i64 = 0
126 while i < 4 { pl[po] = other_ip4[i]; po = po + 1; i = i + 1 }
127 pl[po] = 0 as u8; po = po + 1 // MyAddrCount = 0 (client)
128 return tc_build_fixed(0, TCELL_NETINFO, pl, po, circid_len, out)
129}
130
131// Parse NETINFO payload: extract timestamp + the OtherAddr (type/len/value). Returns 0 or neg.
132func tc_parse_netinfo(pl: *u8, pllen: i64, out_ts: *i64, out_atype: *i64, out_addr: *u8, out_alen: *i64) -> i64 {
133 if pllen < 6 { return TCELL_E_SHORT }
134 out_ts[0] = tc_get_u32(pl, 0)
135 let atype: i64 = pl[4] as i64
136 let alen: i64 = pl[5] as i64
137 if pllen < 6 + alen { return TCELL_E_SHORT }
138 out_atype[0] = atype; out_alen[0] = alen
139 var i: i64 = 0
140 while i < alen { out_addr[i] = pl[6 + i]; i = i + 1 }
141 return 0
142}
143
144// ---- CERTS (cmd 129) parse: N certs, each [CertType:1][CertLen:2][Cert:CertLen] ----
145// Writes cert types + lengths (up to max). Returns cert count, or neg on overrun.
146func tc_parse_certs(pl: *u8, pllen: i64, out_types: *i64, out_lens: *i64, max: i64) -> i64 {
147 if pllen < 1 { return TCELL_E_SHORT }
148 let n: i64 = pl[0] as i64
149 var o: i64 = 1; var i: i64 = 0
150 while i < n {
151 if o + 3 > pllen { return TCELL_E_FORMAT }
152 let ctype: i64 = pl[o] as i64
153 let clen: i64 = tc_get_u16(pl, o + 1)
154 if o + 3 + clen > pllen { return TCELL_E_FORMAT }
155 if i < max { out_types[i] = ctype; out_lens[i] = clen }
156 o = o + 3 + clen; i = i + 1
157 }
158 return n
159}
160
161// ---- AUTH_CHALLENGE (cmd 130) parse: Challenge[32] | N_Methods[2] | Methods[2*N] ----
162// Copies the 32-byte challenge, extracts up to `max` method ids. Returns method count, or neg.
163func tc_parse_auth_challenge(pl: *u8, pllen: i64, out_chal32: *u8, out_methods: *i64, max: i64) -> i64 {
164 if pllen < 34 { return TCELL_E_SHORT }
165 var i: i64 = 0
166 while i < 32 { out_chal32[i] = pl[i]; i = i + 1 }
167 let nm: i64 = tc_get_u16(pl, 32)
168 if pllen < 34 + nm*2 { return TCELL_E_SHORT }
169 var j: i64 = 0
170 while j < nm { if j < max { out_methods[j] = tc_get_u16(pl, 34 + j*2) } j = j + 1 }
171 return nm
172}