| ... | @@ -501,18 +501,34 @@ pub const Ip6Address = struct { | ... | @@ -501,18 +501,34 @@ pub const Ip6Address = struct { |
| 501 | overflow: usize, | 501 | overflow: usize, |
| 502 | }; | 502 | }; |
| 503 | | 503 | |
| 504 | pub fn parse(text: []const u8) Parsed { | 504 | pub fn parse(text_in: []const u8) Parsed { |
| | 505 | var text: []const u8 = text_in; // so we can alias v4_amended if needed |
| 505 | if (text.len < 2) return .incomplete; | 506 | if (text.len < 2) return .incomplete; |
| 506 | const ip4_prefix = "::ffff:"; | 507 | |
| 507 | if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) { | 508 | // Pre-processing for trailing :IPv4 - If there is no "%iface", and |
| 508 | const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch | 509 | // the part after the last ':' has a '.', parse it as ipv4 and |
| 509 | return .{ .invalid_ip4_mapping = ip4_prefix.len }; | 510 | // convert to IPv6 ASCII text in v4_amended as the new "text" |
| 510 | const b = parsed.bytes; | 511 | var v4_amended: [(8 * 4) + 7]u8 = undefined; |
| 511 | return .{ .success = .{ | 512 | if (std.mem.findScalar(u8, text, '%') == null) { |
| 512 | .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] }, | 513 | const first_parts, const last_part = std.mem.cutScalarLast(u8, text, ':') orelse |
| 513 | .interface_name = null, | 514 | return .incomplete; |
| 514 | } }; | 515 | if (std.mem.findScalar(u8, last_part, '.')) |_| { |
| | 516 | if (first_parts.len > (6 * 4) + 5) // hhhh:hhhh:hhhh:hhhh:hhhh:hhhh |
| | 517 | return .{ .invalid_ip4_mapping = first_parts.len }; |
| | 518 | const parsed = Ip4Address.parse(last_part, 0) catch |
| | 519 | return .{ .invalid_ip4_mapping = first_parts.len + 1 }; |
| | 520 | @memcpy(v4_amended[0..first_parts.len], first_parts); |
| | 521 | const v4_part = v4_amended[first_parts.len..][0..10]; |
| | 522 | v4_part[0] = ':'; |
| | 523 | @memcpy(v4_part[1..3], &std.fmt.hex(parsed.bytes[0])); |
| | 524 | @memcpy(v4_part[3..5], &std.fmt.hex(parsed.bytes[1])); |
| | 525 | v4_part[5] = ':'; |
| | 526 | @memcpy(v4_part[6..8], &std.fmt.hex(parsed.bytes[2])); |
| | 527 | @memcpy(v4_part[8..10], &std.fmt.hex(parsed.bytes[3])); |
| | 528 | text = v4_amended[0 .. first_parts.len + 10]; |
| | 529 | } |
| 515 | } | 530 | } |
| | 531 | |
| 516 | // Has to be u16 elements to handle 3-digit hex numbers from compression. | 532 | // Has to be u16 elements to handle 3-digit hex numbers from compression. |
| 517 | var parts: [8]u16 = @splat(0); | 533 | var parts: [8]u16 = @splat(0); |
| 518 | var parts_i: u8 = 0; | 534 | var parts_i: u8 = 0; |
| ... | @@ -608,59 +624,60 @@ pub const Ip6Address = struct { | ... | @@ -608,59 +624,60 @@ pub const Ip6Address = struct { |
| 608 | | 624 | |
| 609 | pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { | 625 | pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { |
| 610 | const bytes = &u.bytes; | 626 | const bytes = &u.bytes; |
| 611 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { | 627 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) |
| 612 | try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); | 628 | return w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 613 | } else { | 629 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 })) |
| 614 | const parts: [8]u16 = .{ | 630 | return w.print("64:ff9b::{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 615 | std.mem.readInt(u16, bytes[0..2], .big), | 631 | |
| 616 | std.mem.readInt(u16, bytes[2..4], .big), | 632 | const parts: [8]u16 = .{ |
| 617 | std.mem.readInt(u16, bytes[4..6], .big), | 633 | std.mem.readInt(u16, bytes[0..2], .big), |
| 618 | std.mem.readInt(u16, bytes[6..8], .big), | 634 | std.mem.readInt(u16, bytes[2..4], .big), |
| 619 | std.mem.readInt(u16, bytes[8..10], .big), | 635 | std.mem.readInt(u16, bytes[4..6], .big), |
| 620 | std.mem.readInt(u16, bytes[10..12], .big), | 636 | std.mem.readInt(u16, bytes[6..8], .big), |
| 621 | std.mem.readInt(u16, bytes[12..14], .big), | 637 | std.mem.readInt(u16, bytes[8..10], .big), |
| 622 | std.mem.readInt(u16, bytes[14..16], .big), | 638 | std.mem.readInt(u16, bytes[10..12], .big), |
| 623 | }; | 639 | std.mem.readInt(u16, bytes[12..14], .big), |
| 624 | | 640 | std.mem.readInt(u16, bytes[14..16], .big), |
| 625 | // Find the longest zero run | 641 | }; |
| 626 | var longest_start: usize = 8; | | |
| 627 | var longest_len: usize = 0; | | |
| 628 | var current_start: usize = 0; | | |
| 629 | var current_len: usize = 0; | | |
| 630 | | | |
| 631 | for (parts, 0..) |part, i| { | | |
| 632 | if (part == 0) { | | |
| 633 | if (current_len == 0) { | | |
| 634 | current_start = i; | | |
| 635 | } | | |
| 636 | current_len += 1; | | |
| 637 | if (current_len > longest_len) { | | |
| 638 | longest_start = current_start; | | |
| 639 | longest_len = current_len; | | |
| 640 | } | | |
| 641 | } else { | | |
| 642 | current_len = 0; | | |
| 643 | } | | |
| 644 | } | | |
| 645 | | 642 | |
| 646 | // Only compress if the longest zero run is 2 or more | 643 | // Find the longest zero run |
| 647 | if (longest_len < 2) { | 644 | var longest_start: usize = 8; |
| 648 | longest_start = 8; | 645 | var longest_len: usize = 0; |
| 649 | longest_len = 0; | 646 | var current_start: usize = 0; |
| 650 | } | 647 | var current_len: usize = 0; |
| 651 | | 648 | |
| 652 | var i: usize = 0; | 649 | for (parts, 0..) |part, i| { |
| 653 | while (parts.len - i != 0) : (i += 1) { | 650 | if (part == 0) { |
| 654 | if (i == longest_start) { | 651 | if (current_len == 0) { |
| 655 | // Emit "::" for the longest zero run | 652 | current_start = i; |
| 656 | try w.writeAll(if (i == 0) "::" else ":"); | | |
| 657 | i += longest_len - 1; // Skip the compressed range | | |
| 658 | continue; | | |
| 659 | } | 653 | } |
| 660 | try w.print("{x}", .{parts[i]}); | 654 | current_len += 1; |
| 661 | if (i != parts.len - 1) { | 655 | if (current_len > longest_len) { |
| 662 | try w.writeAll(":"); | 656 | longest_start = current_start; |
| | 657 | longest_len = current_len; |
| 663 | } | 658 | } |
| | 659 | } else { |
| | 660 | current_len = 0; |
| | 661 | } |
| | 662 | } |
| | 663 | |
| | 664 | // Only compress if the longest zero run is 2 or more |
| | 665 | if (longest_len < 2) { |
| | 666 | longest_start = 8; |
| | 667 | longest_len = 0; |
| | 668 | } |
| | 669 | |
| | 670 | var i: usize = 0; |
| | 671 | while (parts.len - i != 0) : (i += 1) { |
| | 672 | if (i == longest_start) { |
| | 673 | // Emit "::" for the longest zero run |
| | 674 | try w.writeAll(if (i == 0) "::" else ":"); |
| | 675 | i += longest_len - 1; // Skip the compressed range |
| | 676 | continue; |
| | 677 | } |
| | 678 | try w.print("{x}", .{parts[i]}); |
| | 679 | if (i != parts.len - 1) { |
| | 680 | try w.writeAll(":"); |
| 664 | } | 681 | } |
| 665 | } | 682 | } |
| 666 | if (u.interface_name) |n| try w.print("%{s}", .{n}); | 683 | if (u.interface_name) |n| try w.print("%{s}", .{n}); |
| ... | @@ -1582,6 +1599,13 @@ test "parsing IPv6 addresses" { | ... | @@ -1582,6 +1599,13 @@ test "parsing IPv6 addresses" { |
| 1582 | try testIp6Parse("fe80::abcd:ef12%3"); | 1599 | try testIp6Parse("fe80::abcd:ef12%3"); |
| 1583 | try testIp6Parse("ff02::"); | 1600 | try testIp6Parse("ff02::"); |
| 1584 | try testIp6Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); | 1601 | try testIp6Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); |
| | 1602 | try testIp6Parse("::ffff:192.0.2.1"); // IPv4 Mapped |
| | 1603 | try testIp6Parse("64:ff9b::192.0.2.1"); // RFC 6052 Well-Known Prefix |
| | 1604 | try testIp6Parse("fe80::e0e:76ff:fed4:cf22%iface.123"); // edge case for :ipv4 parsing |
| | 1605 | try testIp6ParseTransform("::c000:201", "::192.0.2.1"); // Deprecated "IPv4 Compatible" |
| | 1606 | try testIp6ParseTransform("::ffff:192.0.2.1", "::ffff:c000:201"); // Non-canonical in Mapped |
| | 1607 | try testIp6ParseTransform("2001:db8::c000:201", "2001:db8::192.0.2.1"); // arbitrary prefix |
| | 1608 | try testIp6ParseTransform("2001:db8::201", "2001:db8::0.0.2.1"); // Compression+:IPv4 edge case |
| 1585 | } | 1609 | } |
| 1586 | test "IpAddress.setPort works" { | 1610 | test "IpAddress.setPort works" { |
| 1587 | var addr: IpAddress = .{ .ip4 = undefined }; | 1611 | var addr: IpAddress = .{ .ip4 = undefined }; |