| ... | ... | @@ -501,18 +501,34 @@ pub const Ip6Address = struct { |
| 501 | 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 | 506 | if (text.len < 2) return .incomplete; |
| 506 | | const ip4_prefix = "::ffff:"; |
| 507 | | if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) { |
| 508 | | const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch |
| 509 | | return .{ .invalid_ip4_mapping = ip4_prefix.len }; |
| 510 | | const b = parsed.bytes; |
| 511 | | return .{ .success = .{ |
| 512 | | .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] }, |
| 513 | | .interface_name = null, |
| 514 | | } }; |
| 507 | |
| 508 | // Pre-processing for trailing :IPv4 - If there is no "%iface", and |
| 509 | // the part after the last ':' has a '.', parse it as ipv4 and |
| 510 | // convert to IPv6 ASCII text in v4_amended as the new "text" |
| 511 | var v4_amended: [(8 * 4) + 7]u8 = undefined; |
| 512 | if (std.mem.findScalar(u8, text, '%') == null) { |
| 513 | const first_parts, const last_part = std.mem.cutScalarLast(u8, text, ':') orelse |
| 514 | return .incomplete; |
| 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 | 532 | // Has to be u16 elements to handle 3-digit hex numbers from compression. |
| 517 | 533 | var parts: [8]u16 = @splat(0); |
| 518 | 534 | var parts_i: u8 = 0; |
| ... | ... | @@ -608,59 +624,60 @@ pub const Ip6Address = struct { |
| 608 | 624 | |
| 609 | 625 | pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { |
| 610 | 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 })) { |
| 612 | | try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 613 | | } else { |
| 614 | | const parts: [8]u16 = .{ |
| 615 | | std.mem.readInt(u16, bytes[0..2], .big), |
| 616 | | std.mem.readInt(u16, bytes[2..4], .big), |
| 617 | | std.mem.readInt(u16, bytes[4..6], .big), |
| 618 | | std.mem.readInt(u16, bytes[6..8], .big), |
| 619 | | std.mem.readInt(u16, bytes[8..10], .big), |
| 620 | | std.mem.readInt(u16, bytes[10..12], .big), |
| 621 | | std.mem.readInt(u16, bytes[12..14], .big), |
| 622 | | std.mem.readInt(u16, bytes[14..16], .big), |
| 623 | | }; |
| 624 | | |
| 625 | | // Find the longest zero run |
| 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 | | } |
| 627 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) |
| 628 | return w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 629 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 })) |
| 630 | return w.print("64:ff9b::{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 631 | |
| 632 | const parts: [8]u16 = .{ |
| 633 | std.mem.readInt(u16, bytes[0..2], .big), |
| 634 | std.mem.readInt(u16, bytes[2..4], .big), |
| 635 | std.mem.readInt(u16, bytes[4..6], .big), |
| 636 | std.mem.readInt(u16, bytes[6..8], .big), |
| 637 | std.mem.readInt(u16, bytes[8..10], .big), |
| 638 | std.mem.readInt(u16, bytes[10..12], .big), |
| 639 | std.mem.readInt(u16, bytes[12..14], .big), |
| 640 | std.mem.readInt(u16, bytes[14..16], .big), |
| 641 | }; |
| 645 | 642 | |
| 646 | | // Only compress if the longest zero run is 2 or more |
| 647 | | if (longest_len < 2) { |
| 648 | | longest_start = 8; |
| 649 | | longest_len = 0; |
| 650 | | } |
| 643 | // Find the longest zero run |
| 644 | var longest_start: usize = 8; |
| 645 | var longest_len: usize = 0; |
| 646 | var current_start: usize = 0; |
| 647 | var current_len: usize = 0; |
| 651 | 648 | |
| 652 | | var i: usize = 0; |
| 653 | | while (parts.len - i != 0) : (i += 1) { |
| 654 | | if (i == longest_start) { |
| 655 | | // Emit "::" for the longest zero run |
| 656 | | try w.writeAll(if (i == 0) "::" else ":"); |
| 657 | | i += longest_len - 1; // Skip the compressed range |
| 658 | | continue; |
| 649 | for (parts, 0..) |part, i| { |
| 650 | if (part == 0) { |
| 651 | if (current_len == 0) { |
| 652 | current_start = i; |
| 659 | 653 | } |
| 660 | | try w.print("{x}", .{parts[i]}); |
| 661 | | if (i != parts.len - 1) { |
| 662 | | try w.writeAll(":"); |
| 654 | current_len += 1; |
| 655 | if (current_len > longest_len) { |
| 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 | 683 | if (u.interface_name) |n| try w.print("%{s}", .{n}); |
| ... | ... | @@ -1582,6 +1599,13 @@ test "parsing IPv6 addresses" { |
| 1582 | 1599 | try testIp6Parse("fe80::abcd:ef12%3"); |
| 1583 | 1600 | try testIp6Parse("ff02::"); |
| 1584 | 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 | 1610 | test "IpAddress.setPort works" { |
| 1587 | 1611 | var addr: IpAddress = .{ .ip4 = undefined }; |