ipaddr.nx source
↩ module page · 342 lines · 11492 B
1// ipaddr.nx -- IPv4 + IPv6 address parse / format.
2//
3// Used by: net.nx (connect / bind with human-readable addresses),
4// X.509 subjectAltName IP entries, HTTP Host header validation,
5// access-control lists.
6//
7// IPv4: dotted quad "a.b.c.d" where each octet is 0..255.
8// Packed as network-order u32 (big-endian): a in high byte.
9//
10// IPv6: colon-separated "1234:abcd:..." with optional "::" run-
11// length compression. 16-byte value (8 x u16 hextets).
12// Parser handles both full + compressed forms; emitter
13// always produces compressed canonical form (RFC 5952).
14//
15// Invariants:
16// IP1 All decoders are bounds-checked; malformed input returns
17// a negative error code, never garbage.
18// IP2 Encoders never overflow the output buffer -- caller
19// guarantees >= 16 chars for IPv4 ("255.255.255.255\0"),
20// >= 40 chars for IPv6 (":ffff:ffff:...:ffff\0").
21// IP3 Round-trip exact for canonical forms. Compressed IPv6
22// that uses "::" is normalised on re-format; the byte-level
23// value round-trips even if the string doesn't.
24// IP4 No IPv4-mapped IPv6 (::ffff:a.b.c.d) handling today.
25// That form is RFC 4291 but complicates parsing; callers
26// needing it can decompose manually.
27
28import "syscalls.nx"
29
30const IPADDR_ERR_FORMAT: i64 = -1
31const IPADDR_ERR_RANGE: i64 = -2
32
33// ---- IPv4 --------------------------------------------------------
34
35// Parse "a.b.c.d" into a 32-bit integer (a in high byte = network
36// byte order). Returns the u32 value, or negative IPADDR_ERR_*.
37func ipv4_parse(s: *u8, n: i64) -> i64 {
38 var pos: i64 = 0
39 var octets: i64 = 0
40 var result: i64 = 0
41 var octet_val: i64 = 0
42 var octet_digits: i64 = 0
43 while pos < n {
44 let c: i64 = s[pos]
45 if c == 0x2E { // '.'
46 if octet_digits == 0 { return IPADDR_ERR_FORMAT }
47 if octet_val > 255 { return IPADDR_ERR_RANGE }
48 result = (result << 8) | octet_val
49 octets = octets + 1
50 if octets >= 4 { return IPADDR_ERR_FORMAT }
51 octet_val = 0
52 octet_digits = 0
53 pos = pos + 1
54 } else {
55 if c >= 0x30 {
56 if c <= 0x39 {
57 octet_val = octet_val * 10 + (c - 0x30)
58 octet_digits = octet_digits + 1
59 if octet_digits > 3 { return IPADDR_ERR_FORMAT }
60 pos = pos + 1
61 } else { return IPADDR_ERR_FORMAT }
62 } else { return IPADDR_ERR_FORMAT }
63 }
64 }
65 // Final octet.
66 if octet_digits == 0 { return IPADDR_ERR_FORMAT }
67 if octet_val > 255 { return IPADDR_ERR_RANGE }
68 if octets != 3 { return IPADDR_ERR_FORMAT }
69 result = (result << 8) | octet_val
70 return result & 0xFFFFFFFF
71}
72
73// Forward declaration for ip_emit_dec (defined after ipv4_format).
74func ip_emit_dec(out: *u8, off: i64, n: i64) -> i64;
75
76// Format a 32-bit IPv4 address as "a.b.c.d". Returns bytes written
77// (4..15). Caller provides >= 16 bytes of space.
78func ipv4_format(ip: i64, out: *u8) -> i64 {
79 let a: i64 = (ip >> 24) & 0xFF
80 let b: i64 = (ip >> 16) & 0xFF
81 let c: i64 = (ip >> 8) & 0xFF
82 let d: i64 = ip & 0xFF
83 var pos: i64 = 0
84 pos = pos + ip_emit_dec(out, pos, a)
85 out[pos] = 0x2E; pos = pos + 1
86 pos = pos + ip_emit_dec(out, pos, b)
87 out[pos] = 0x2E; pos = pos + 1
88 pos = pos + ip_emit_dec(out, pos, c)
89 out[pos] = 0x2E; pos = pos + 1
90 pos = pos + ip_emit_dec(out, pos, d)
91 return pos
92}
93
94// Emit 0..255 as decimal. Returns digits written (1..3).
95func ip_emit_dec(out: *u8, off: i64, n: i64) -> i64 {
96 if n < 10 {
97 out[off] = 0x30 + n
98 return 1
99 }
100 if n < 100 {
101 out[off] = 0x30 + (n / 10)
102 out[off + 1] = 0x30 + (n % 10)
103 return 2
104 }
105 out[off] = 0x30 + (n / 100)
106 out[off + 1] = 0x30 + ((n / 10) % 10)
107 out[off + 2] = 0x30 + (n % 10)
108 return 3
109}
110
111// ---- IPv6 --------------------------------------------------------
112//
113// 16 bytes packed into 8 hextets, big-endian. Output buffer is
114// caller-owned 16 bytes; each pair of bytes [0-1], [2-3], etc. is
115// one hextet in network byte order.
116
117// Parse one hextet from 1-4 hex digits starting at s[pos]. On
118// success writes value to *out_val and advances *pos past the
119// digits. Returns 0 / negative on error.
120func ipv6_parse_hex(s: *u8, n: i64, pos: *i64, out_val: *i64) -> i64 {
121 var p: i64 = *pos
122 var val: i64 = 0
123 var digits: i64 = 0
124 while p < n {
125 let c: i64 = s[p]
126 var d: i64 = -1
127 if c >= 0x30 { if c <= 0x39 { d = c - 0x30 } }
128 if c >= 0x41 { if c <= 0x46 { d = c - 0x41 + 10 } }
129 if c >= 0x61 { if c <= 0x66 { d = c - 0x61 + 10 } }
130 if d < 0 {
131 if digits == 0 { return IPADDR_ERR_FORMAT }
132 *out_val = val
133 *pos = p
134 return 0
135 }
136 val = (val << 4) | d
137 digits = digits + 1
138 if digits > 4 { return IPADDR_ERR_FORMAT }
139 p = p + 1
140 }
141 if digits == 0 { return IPADDR_ERR_FORMAT }
142 *out_val = val
143 *pos = p
144 return 0
145}
146
147// Parse an IPv6 address into the caller-supplied 16-byte buffer.
148// Handles the "::" run-length compression. Returns 0 on success
149// or negative IPADDR_ERR_*.
150func ipv6_parse(s: *u8, n: i64, out: *u8) -> i64 {
151 var pos: i64 = 0
152 // left[] holds hextets before "::" (or all of them if no "::").
153 let left_raw: *u8 = sys_mmap(64)
154 let left: *i64 = left_raw as *i64
155 // right[] holds hextets after "::".
156 let right_raw: *u8 = sys_mmap(64)
157 let right: *i64 = right_raw as *i64
158 var n_left: i64 = 0
159 var n_right: i64 = 0
160 var seen_double_colon: i64 = 0
161
162 // Leading "::" handled explicitly.
163 if pos + 1 < n {
164 if s[pos] == 0x3A {
165 if s[pos + 1] == 0x3A {
166 seen_double_colon = 1
167 pos = pos + 2
168 }
169 }
170 }
171
172 // Parse hextets.
173 while pos < n {
174 let val_p: *i64 = sys_mmap(16) as *i64
175 let pos_p: *i64 = sys_mmap(16) as *i64
176 *pos_p = pos
177 let rc: i64 = ipv6_parse_hex(s, n, pos_p, val_p)
178 if rc < 0 { return rc }
179 pos = *pos_p
180 if seen_double_colon == 0 {
181 left[n_left] = *val_p
182 n_left = n_left + 1
183 } else {
184 right[n_right] = *val_p
185 n_right = n_right + 1
186 }
187 if pos >= n { pos = n }
188 if pos < n {
189 if s[pos] == 0x3A {
190 if pos + 1 < n {
191 if s[pos + 1] == 0x3A {
192 if seen_double_colon == 1 { return IPADDR_ERR_FORMAT }
193 seen_double_colon = 1
194 pos = pos + 2
195 } else {
196 pos = pos + 1
197 }
198 } else {
199 pos = pos + 1
200 }
201 } else {
202 return IPADDR_ERR_FORMAT
203 }
204 }
205 }
206
207 let total: i64 = n_left + n_right
208 if seen_double_colon == 0 {
209 if total != 8 { return IPADDR_ERR_FORMAT }
210 } else {
211 if total > 8 { return IPADDR_ERR_FORMAT }
212 }
213
214 // Compose 16 bytes.
215 var i: i64 = 0
216 while i < 16 { out[i] = 0; i = i + 1 }
217 i = 0
218 while i < n_left {
219 out[i * 2] = (left[i] >> 8) & 0xFF
220 out[i * 2 + 1] = left[i] & 0xFF
221 i = i + 1
222 }
223 let right_start: i64 = 8 - n_right
224 i = 0
225 while i < n_right {
226 out[(right_start + i) * 2] = (right[i] >> 8) & 0xFF
227 out[(right_start + i) * 2 + 1] = right[i] & 0xFF
228 i = i + 1
229 }
230 return 0
231}
232
233// Format an IPv6 address (16 bytes) into its canonical RFC 5952
234// compressed form. Rules (RFC 5952 ยง4):
235// - Lowercase hex
236// - Suppress leading zeros in each hextet
237// - Replace the LONGEST run of consecutive all-zero hextets
238// with "::" (ties: leftmost run)
239// - "::" is used only once
240// - At least two consecutive zero hextets required for "::";
241// a single zero hextet is still written as "0"
242// Returns bytes written to `out`.
243func ipv6_format(bytes: *u8, out: *u8) -> i64 {
244 // Extract the 8 hextets.
245 let h_raw: *u8 = sys_mmap(64)
246 let h: *i64 = h_raw as *i64
247 var i: i64 = 0
248 while i < 8 {
249 h[i] = (bytes[i * 2] << 8) | bytes[i * 2 + 1]
250 i = i + 1
251 }
252
253 // Find the longest run of consecutive zero hextets. Must be
254 // >= 2 to qualify for "::" compression.
255 var best_start: i64 = -1
256 var best_len: i64 = 0
257 var run_start: i64 = -1
258 var run_len: i64 = 0
259 i = 0
260 while i < 8 {
261 if h[i] == 0 {
262 if run_start < 0 { run_start = i }
263 run_len = run_len + 1
264 if run_len > best_len {
265 best_start = run_start
266 best_len = run_len
267 }
268 } else {
269 run_start = -1
270 run_len = 0
271 }
272 i = i + 1
273 }
274 if best_len < 2 { best_start = -1 }
275
276 // Emit each hextet; substitute "::" at the compressed run.
277 var pos: i64 = 0
278 i = 0
279 while i < 8 {
280 if i == best_start {
281 // Emit "::" and skip the run.
282 if i == 0 { out[pos] = 0x3A; pos = pos + 1 }
283 out[pos] = 0x3A
284 pos = pos + 1
285 i = i + best_len
286 } else {
287 if i > 0 {
288 out[pos] = 0x3A
289 pos = pos + 1
290 }
291 // Emit hextet as 1..4 lowercase hex digits, no leading zeros.
292 let v: i64 = h[i]
293 if v == 0 {
294 out[pos] = 0x30
295 pos = pos + 1
296 } else {
297 var emitted: i64 = 0
298 var shift: i64 = 12
299 while shift >= 0 {
300 let nib: i64 = (v >> shift) & 0xF
301 if emitted == 0 {
302 if nib != 0 {
303 var c: i64 = 0x30 + nib
304 if nib > 9 { c = 0x61 + nib - 10 }
305 out[pos] = c
306 pos = pos + 1
307 emitted = 1
308 }
309 } else {
310 var c: i64 = 0x30 + nib
311 if nib > 9 { c = 0x61 + nib - 10 }
312 out[pos] = c
313 pos = pos + 1
314 }
315 shift = shift - 4
316 }
317 }
318 i = i + 1
319 }
320 }
321 return pos
322}
323
324// Compile-only smoke.
325func main() -> i64 {
326 // IPv4 round-trip: 192.168.1.1 -> 0xC0A80101 -> "192.168.1.1".
327 let v4: i64 = ipv4_parse("192.168.1.1", 11)
328 if v4 != 0xC0A80101 { return 1 }
329 let out: *u8 = sys_mmap(32)
330 let n: i64 = ipv4_format(v4, out)
331 if n != 11 { return 2 }
332 if out[0] != 0x31 { return 3 } // '1'
333 if out[3] != 0x2E { return 4 } // '.'
334
335 // IPv6 parse: "::1" (loopback) = 15 zero bytes + 0x01.
336 let v6: *u8 = sys_mmap(16)
337 let rc: i64 = ipv6_parse("::1", 3, v6)
338 if rc != 0 { return 5 }
339 if v6[15] != 0x01 { return 6 }
340 if v6[0] != 0 { return 7 }
341 return 0
342}