code wiki / (root) / ipaddr.nx

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}