| ... | @@ -30,8 +30,8 @@ pub const IpAddress = union(enum) { | ... | @@ -30,8 +30,8 @@ pub const IpAddress = union(enum) { |
| 30 | /// This is a pure function but it cannot handle IPv6 addresses that have | 30 | /// This is a pure function but it cannot handle IPv6 addresses that have |
| 31 | /// scope ids ("%foo" at the end). To also handle those, `resolve` must be | 31 | /// scope ids ("%foo" at the end). To also handle those, `resolve` must be |
| 32 | /// called instead. | 32 | /// called instead. |
| 33 | pub fn parse(name: []const u8, port: u16) !IpAddress { | 33 | pub fn parse(text: []const u8, port: u16) !IpAddress { |
| 34 | if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { | 34 | if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) { |
| 35 | error.Overflow, | 35 | error.Overflow, |
| 36 | error.InvalidEnd, | 36 | error.InvalidEnd, |
| 37 | error.InvalidCharacter, | 37 | error.InvalidCharacter, |
| ... | @@ -40,7 +40,7 @@ pub const IpAddress = union(enum) { | ... | @@ -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 | pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress { | 46 | pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress { |
| ... | @@ -227,39 +227,102 @@ pub const Ip6Address = struct { | ... | @@ -227,39 +227,102 @@ pub const Ip6Address = struct { |
| 227 | success: Unresolved, | 227 | success: Unresolved, |
| 228 | invalid_byte: usize, | 228 | invalid_byte: usize, |
| 229 | unexpected_end, | 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 { | 234 | pub fn parse(text: []const u8) Parsed { |
| 233 | if (buffer.len < 2) return .unexpected_end; | 235 | if (text.len < 2) return .unexpected_end; |
| | 236 | // Has to be u16 elements to handle 3-digit hex numbers from compression. |
| 234 | var parts: [8]u16 = @splat(0); | 237 | var parts: [8]u16 = @splat(0); |
| 235 | var parts_i: usize = 0; | 238 | var parts_i: u8 = 0; |
| 236 | var i: usize = 0; | 239 | var text_i: u8 = 0; |
| 237 | var digit_i: usize = 0; | 240 | var digit_i: u8 = 0; |
| 238 | const State = union(enum) { digit, colon, end }; | 241 | var compress_start: ?u8 = null; |
| | 242 | var interface_name_text: ?[]const u8 = null; |
| | 243 | const State = union(enum) { digit, end }; |
| 239 | state: switch (State.digit) { | 244 | state: switch (State.digit) { |
| 240 | .digit => c: switch (buffer[i]) { | 245 | .digit => c: switch (text[text_i]) { |
| 241 | 'a'...'f' => |c| { | 246 | 'a'...'f' => |c| { |
| 242 | const digit = c - 'a'; | 247 | const digit = c - 'a' + 10; |
| 243 | parts[parts_i] = parts[parts_i] * 16 + digit; | 248 | parts[parts_i] = parts[parts_i] * 16 + digit; |
| 244 | if (digit_i == 3) { | 249 | if (digit_i == 4) return .{ .invalid_byte = text_i }; |
| 245 | digit_i = 0; | 250 | digit_i += 1; |
| | 251 | text_i += 1; |
| | 252 | if (text.len - text_i == 0) { |
| 246 | parts_i += 1; | 253 | parts_i += 1; |
| 247 | i += 1; | 254 | continue :state .end; |
| 248 | if (parts.len - parts_i == 0) continue :state .end; | | |
| 249 | continue :state .colon; | | |
| 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 | digit_i += 1; | 263 | digit_i += 1; |
| 252 | if (buffer.len - i == 0) return .unexpected_end; | 264 | text_i += 1; |
| 253 | i += 1; | 265 | if (text.len - text_i == 0) { |
| 254 | continue :c buffer[i]; | 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'), | 302 | else => return .{ .invalid_byte = text_i }, |
| 257 | '0'...'9' => |c| continue :c c + ('a' - '0'), | 303 | }, |
| 258 | ':' => @panic("TODO"), | 304 | .end => { |
| 259 | else => return .{ .invalid_byte = i }, | 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,7 +382,6 @@ pub const Ip6Address = struct { |
| 319 | longest_len = 0; | 382 | longest_len = 0; |
| 320 | } | 383 | } |
| 321 | | 384 | |
| 322 | try w.writeAll("["); | | |
| 323 | var i: usize = 0; | 385 | var i: usize = 0; |
| 324 | var abbrv = false; | 386 | var abbrv = false; |
| 325 | while (i < parts.len) : (i += 1) { | 387 | while (i < parts.len) : (i += 1) { |
| ... | @@ -525,6 +587,12 @@ pub const Interface = struct { | ... | @@ -525,6 +587,12 @@ pub const Interface = struct { |
| 525 | | 587 | |
| 526 | pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name { | 588 | pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name { |
| 527 | if (bytes.len > max_len) return error.NameTooLong; | 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 | var result: Name = undefined; | 596 | var result: Name = undefined; |
| 529 | @memcpy(result.bytes[0..bytes.len], bytes); | 597 | @memcpy(result.bytes[0..bytes.len], bytes); |
| 530 | result.bytes[bytes.len] = 0; | 598 | result.bytes[bytes.len] = 0; |
| ... | @@ -673,6 +741,24 @@ pub const Server = struct { | ... | @@ -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 | test { | 762 | test { |
| 677 | _ = HostName; | 763 | _ = HostName; |
| 678 | } | 764 | } |