Skip to content

Commit 7700744

Browse files
authored
std.net: Fix IPv6 address format compression for long zero runs (#22441)
1 parent 220f80e commit 7700744

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

lib/std/net.zig

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,17 +683,50 @@ pub const Ip6Address = extern struct {
683683
break :blk buf;
684684
},
685685
};
686+
687+
// Find the longest zero run
688+
var longest_start: usize = 8;
689+
var longest_len: usize = 0;
690+
var current_start: usize = 0;
691+
var current_len: usize = 0;
692+
693+
for (native_endian_parts, 0..) |part, i| {
694+
if (part == 0) {
695+
if (current_len == 0) {
696+
current_start = i;
697+
}
698+
current_len += 1;
699+
if (current_len > longest_len) {
700+
longest_start = current_start;
701+
longest_len = current_len;
702+
}
703+
} else {
704+
current_len = 0;
705+
}
706+
}
707+
708+
// Only compress if the longest zero run is 2 or more
709+
if (longest_len < 2) {
710+
longest_start = 8;
711+
longest_len = 0;
712+
}
713+
686714
try out_stream.writeAll("[");
687715
var i: usize = 0;
688716
var abbrv = false;
689717
while (i < native_endian_parts.len) : (i += 1) {
690-
if (native_endian_parts[i] == 0) {
718+
if (i == longest_start) {
719+
// Emit "::" for the longest zero run
691720
if (!abbrv) {
692721
try out_stream.writeAll(if (i == 0) "::" else ":");
693722
abbrv = true;
694723
}
724+
i += longest_len - 1; // Skip the compressed range
695725
continue;
696726
}
727+
if (abbrv) {
728+
abbrv = false;
729+
}
697730
try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]});
698731
if (i != native_endian_parts.len - 1) {
699732
try out_stream.writeAll(":");

lib/std/net/test.zig

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,62 @@ test "parse and render IP addresses at comptime" {
2626
}
2727
}
2828

29+
test "format IPv6 address with no zero runs" {
30+
if (builtin.os.tag == .wasi) return error.SkipZigTest;
31+
32+
const addr = try std.net.Address.parseIp6("2001:db8:1:2:3:4:5:6", 0);
33+
34+
var buffer: [50]u8 = undefined;
35+
const result = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;
36+
37+
try std.testing.expectEqualStrings("[2001:db8:1:2:3:4:5:6]:0", result);
38+
}
39+
40+
test "parse IPv6 addresses and check compressed form" {
41+
if (builtin.os.tag == .wasi) return error.SkipZigTest;
42+
43+
const alloc = testing.allocator;
44+
45+
// 1) Parse an IPv6 address that should compress to [2001:db8::1:0:0:2]:0
46+
const addr1 = try std.net.Address.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0);
47+
48+
// 2) Parse an IPv6 address that should compress to [2001:db8::1:2]:0
49+
const addr2 = try std.net.Address.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0);
50+
51+
// 3) Parse an IPv6 address that should compress to [2001:db8:1:0:1::2]:0
52+
const addr3 = try std.net.Address.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0);
53+
54+
// Print each address in Zig's default "[ipv6]:port" form.
55+
const printed1 = try std.fmt.allocPrint(alloc, "{any}", .{addr1});
56+
defer testing.allocator.free(printed1);
57+
const printed2 = try std.fmt.allocPrint(alloc, "{any}", .{addr2});
58+
defer testing.allocator.free(printed2);
59+
const printed3 = try std.fmt.allocPrint(alloc, "{any}", .{addr3});
60+
defer testing.allocator.free(printed3);
61+
62+
// Check the exact compressed forms we expect.
63+
try std.testing.expectEqualStrings("[2001:db8::1:0:0:2]:0", printed1);
64+
try std.testing.expectEqualStrings("[2001:db8::1:2]:0", printed2);
65+
try std.testing.expectEqualStrings("[2001:db8:1:0:1::2]:0", printed3);
66+
}
67+
68+
test "parse IPv6 address, check raw bytes" {
69+
if (builtin.os.tag == .wasi) return error.SkipZigTest;
70+
71+
const expected_raw: [16]u8 = .{
72+
0x20, 0x01, 0x0d, 0xb8, // 2001:db8
73+
0x00, 0x00, 0x00, 0x00, // :0000:0000
74+
0x00, 0x01, 0x00, 0x00, // :0001:0000
75+
0x00, 0x00, 0x00, 0x02, // :0000:0002
76+
};
77+
78+
const addr = try std.net.Address.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);
79+
80+
const actual_raw = addr.in6.sa.addr[0..];
81+
try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw);
82+
83+
}
84+
2985
test "parse and render IPv6 addresses" {
3086
if (builtin.os.tag == .wasi) return error.SkipZigTest;
3187

0 commit comments

Comments
 (0)