| ... | ... | @@ -30,8 +30,8 @@ pub const IpAddress = union(enum) { |
| 30 | 30 | /// This is a pure function but it cannot handle IPv6 addresses that have |
| 31 | 31 | /// scope ids ("%foo" at the end). To also handle those, `resolve` must be |
| 32 | 32 | /// called instead. |
| 33 | | pub fn parse(name: []const u8, port: u16) !IpAddress { |
| 34 | | if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { |
| 33 | pub fn parse(text: []const u8, port: u16) !IpAddress { |
| 34 | if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) { |
| 35 | 35 | error.Overflow, |
| 36 | 36 | error.InvalidEnd, |
| 37 | 37 | error.InvalidCharacter, |
| ... | ... | @@ -40,7 +40,7 @@ pub const IpAddress = union(enum) { |
| 40 | 40 | => {}, |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | | return parseIp6(name, port); |
| 43 | return parseIp6(text, port); |
| 44 | 44 | } |
| 45 | 45 | |
| 46 | 46 | pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress { |
| ... | ... | @@ -227,39 +227,102 @@ pub const Ip6Address = struct { |
| 227 | 227 | success: Unresolved, |
| 228 | 228 | invalid_byte: usize, |
| 229 | 229 | unexpected_end, |
| 230 | junk_after_end: usize, |
| 231 | interface_name_oversized: usize, |
| 230 | 232 | }; |
| 231 | 233 | |
| 232 | | pub fn parse(buffer: []const u8) Parsed { |
| 233 | | if (buffer.len < 2) return .unexpected_end; |
| 234 | pub fn parse(text: []const u8) Parsed { |
| 235 | if (text.len < 2) return .unexpected_end; |
| 236 | // Has to be u16 elements to handle 3-digit hex numbers from compression. |
| 234 | 237 | var parts: [8]u16 = @splat(0); |
| 235 | | var parts_i: usize = 0; |
| 236 | | var i: usize = 0; |
| 237 | | var digit_i: usize = 0; |
| 238 | | const State = union(enum) { digit, colon, end }; |
| 238 | var parts_i: u8 = 0; |
| 239 | var text_i: u8 = 0; |
| 240 | var digit_i: u8 = 0; |
| 241 | var compress_start: ?u8 = null; |
| 242 | var interface_name_text: ?[]const u8 = null; |
| 243 | const State = union(enum) { digit, end }; |
| 239 | 244 | state: switch (State.digit) { |
| 240 | | .digit => c: switch (buffer[i]) { |
| 245 | .digit => c: switch (text[text_i]) { |
| 241 | 246 | 'a'...'f' => |c| { |
| 242 | | const digit = c - 'a'; |
| 247 | const digit = c - 'a' + 10; |
| 243 | 248 | parts[parts_i] = parts[parts_i] * 16 + digit; |
| 244 | | if (digit_i == 3) { |
| 245 | | digit_i = 0; |
| 249 | if (digit_i == 4) return .{ .invalid_byte = text_i }; |
| 250 | digit_i += 1; |
| 251 | text_i += 1; |
| 252 | if (text.len - text_i == 0) { |
| 246 | 253 | parts_i += 1; |
| 247 | | i += 1; |
| 248 | | if (parts.len - parts_i == 0) continue :state .end; |
| 249 | | continue :state .colon; |
| 254 | continue :state .end; |
| 250 | 255 | } |
| 256 | continue :c text[text_i]; |
| 257 | }, |
| 258 | 'A'...'F' => |c| continue :c c - 'A' + 'a', |
| 259 | '0'...'9' => |c| { |
| 260 | const digit = c - '0'; |
| 261 | parts[parts_i] = parts[parts_i] * 16 + digit; |
| 262 | if (digit_i == 4) return .{ .invalid_byte = text_i }; |
| 251 | 263 | digit_i += 1; |
| 252 | | if (buffer.len - i == 0) return .unexpected_end; |
| 253 | | i += 1; |
| 254 | | continue :c buffer[i]; |
| 264 | text_i += 1; |
| 265 | if (text.len - text_i == 0) { |
| 266 | parts_i += 1; |
| 267 | continue :state .end; |
| 268 | } |
| 269 | continue :c text[text_i]; |
| 270 | }, |
| 271 | ':' => { |
| 272 | if (digit_i == 0) { |
| 273 | if (compress_start != null) return .{ .invalid_byte = text_i }; |
| 274 | if (text_i == 0) { |
| 275 | text_i += 1; |
| 276 | if (text[text_i] != ':') return .{ .invalid_byte = text_i }; |
| 277 | assert(parts_i == 0); |
| 278 | } |
| 279 | text_i += 1; |
| 280 | if (text.len - text_i == 0) return .unexpected_end; |
| 281 | compress_start = parts_i; |
| 282 | continue :c text[text_i]; |
| 283 | } else { |
| 284 | parts_i += 1; |
| 285 | if (parts.len - parts_i == 0) continue :state .end; |
| 286 | digit_i = 0; |
| 287 | text_i += 1; |
| 288 | if (text.len - text_i == 0) return .unexpected_end; |
| 289 | continue :c text[text_i]; |
| 290 | } |
| 291 | }, |
| 292 | '%' => { |
| 293 | if (digit_i == 0) return .{ .invalid_byte = text_i }; |
| 294 | parts_i += 1; |
| 295 | text_i += 1; |
| 296 | const name = text[text_i..]; |
| 297 | if (name.len > Interface.Name.max_len) return .{ .interface_name_oversized = text_i }; |
| 298 | interface_name_text = name; |
| 299 | text_i = @intCast(text.len); |
| 300 | continue :state .end; |
| 255 | 301 | }, |
| 256 | | 'A'...'F' => |c| continue :c c + ('a' - 'A'), |
| 257 | | '0'...'9' => |c| continue :c c + ('a' - '0'), |
| 258 | | ':' => @panic("TODO"), |
| 259 | | else => return .{ .invalid_byte = i }, |
| 302 | else => return .{ .invalid_byte = text_i }, |
| 303 | }, |
| 304 | .end => { |
| 305 | if (text.len - text_i != 0) return .{ .junk_after_end = text_i }; |
| 306 | const remaining = parts.len - parts_i; |
| 307 | if (compress_start) |s| { |
| 308 | const src = parts[s..parts_i]; |
| 309 | @memmove(parts[parts.len - src.len ..], src); |
| 310 | @memset(parts[s..][0..remaining], 0); |
| 311 | } else { |
| 312 | if (remaining != 0) return .unexpected_end; |
| 313 | } |
| 314 | |
| 315 | // Workaround that can be removed when this proposal is |
| 316 | // implemented https://github.com/ziglang/zig/issues/19755 |
| 317 | if ((comptime @import("builtin").cpu.arch.endian()) != .big) { |
| 318 | for (&parts) |*part| part.* = @byteSwap(part.*); |
| 319 | } |
| 320 | |
| 321 | return .{ .success = .{ |
| 322 | .bytes = @bitCast(parts), |
| 323 | .interface_name = if (interface_name_text) |t| .fromSliceUnchecked(t) else null, |
| 324 | } }; |
| 260 | 325 | }, |
| 261 | | .colon => @panic("TODO"), |
| 262 | | .end => @panic("TODO"), |
| 263 | 326 | } |
| 264 | 327 | } |
| 265 | 328 | |
| ... | ... | @@ -319,7 +382,6 @@ pub const Ip6Address = struct { |
| 319 | 382 | longest_len = 0; |
| 320 | 383 | } |
| 321 | 384 | |
| 322 | | try w.writeAll("["); |
| 323 | 385 | var i: usize = 0; |
| 324 | 386 | var abbrv = false; |
| 325 | 387 | while (i < parts.len) : (i += 1) { |
| ... | ... | @@ -525,6 +587,12 @@ pub const Interface = struct { |
| 525 | 587 | |
| 526 | 588 | pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name { |
| 527 | 589 | if (bytes.len > max_len) return error.NameTooLong; |
| 590 | return .fromSliceUnchecked(bytes); |
| 591 | } |
| 592 | |
| 593 | /// Asserts bytes.len fits in `max_len`. |
| 594 | pub fn fromSliceUnchecked(bytes: []const u8) Name { |
| 595 | assert(bytes.len <= max_len); |
| 528 | 596 | var result: Name = undefined; |
| 529 | 597 | @memcpy(result.bytes[0..bytes.len], bytes); |
| 530 | 598 | result.bytes[bytes.len] = 0; |
| ... | ... | @@ -673,6 +741,24 @@ pub const Server = struct { |
| 673 | 741 | } |
| 674 | 742 | }; |
| 675 | 743 | |
| 744 | test "parsing IPv6 addresses" { |
| 745 | try testIp6Parse("fe80::e0e:76ff:fed4:cf22%eno1"); |
| 746 | try testIp6Parse("2001:db8::1"); |
| 747 | } |
| 748 | |
| 749 | fn testIp6Parse(text: []const u8) !void { |
| 750 | const ua = switch (Ip6Address.Unresolved.parse(text)) { |
| 751 | .success => |p| p, |
| 752 | else => |x| { |
| 753 | std.debug.print("failed to parse \"{s}\": {any}\n", .{ text, x }); |
| 754 | return error.TestFailed; |
| 755 | }, |
| 756 | }; |
| 757 | var buffer: [100]u8 = undefined; |
| 758 | const result = try std.fmt.bufPrint(&buffer, "{f}", .{ua}); |
| 759 | try std.testing.expectEqualStrings(text, result); |
| 760 | } |
| 761 | |
| 676 | 762 | test { |
| 677 | 763 | _ = HostName; |
| 678 | 764 | } |