diff --git a/lib/std/net.zig b/lib/std/net.zig index 3c956c8c2e5e..5f609496a97a 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -683,17 +683,50 @@ pub const Ip6Address = extern struct { break :blk buf; }, }; + + // Find the longest zero run + var longest_start: usize = 8; + var longest_len: usize = 0; + var current_start: usize = 0; + var current_len: usize = 0; + + for (native_endian_parts, 0..) |part, i| { + if (part == 0) { + if (current_len == 0) { + current_start = i; + } + current_len += 1; + if (current_len > longest_len) { + longest_start = current_start; + longest_len = current_len; + } + } else { + current_len = 0; + } + } + + // Only compress if the longest zero run is 2 or more + if (longest_len < 2) { + longest_start = 8; + longest_len = 0; + } + try out_stream.writeAll("["); var i: usize = 0; var abbrv = false; while (i < native_endian_parts.len) : (i += 1) { - if (native_endian_parts[i] == 0) { + if (i == longest_start) { + // Emit "::" for the longest zero run if (!abbrv) { try out_stream.writeAll(if (i == 0) "::" else ":"); abbrv = true; } + i += longest_len - 1; // Skip the compressed range continue; } + if (abbrv) { + abbrv = false; + } try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]}); if (i != native_endian_parts.len - 1) { try out_stream.writeAll(":"); diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 81b540b917ba..b413d5a46383 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -26,6 +26,62 @@ test "parse and render IP addresses at comptime" { } } +test "format IPv6 address with no zero runs" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + const addr = try std.net.Address.parseIp6("2001:db8:1:2:3:4:5:6", 0); + + var buffer: [50]u8 = undefined; + const result = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable; + + try std.testing.expectEqualStrings("[2001:db8:1:2:3:4:5:6]:0", result); +} + +test "parse IPv6 addresses and check compressed form" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + const alloc = testing.allocator; + + // 1) Parse an IPv6 address that should compress to [2001:db8::1:0:0:2]:0 + const addr1 = try std.net.Address.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0); + + // 2) Parse an IPv6 address that should compress to [2001:db8::1:2]:0 + const addr2 = try std.net.Address.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0); + + // 3) Parse an IPv6 address that should compress to [2001:db8:1:0:1::2]:0 + const addr3 = try std.net.Address.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0); + + // Print each address in Zig's default "[ipv6]:port" form. + const printed1 = try std.fmt.allocPrint(alloc, "{any}", .{addr1}); + defer testing.allocator.free(printed1); + const printed2 = try std.fmt.allocPrint(alloc, "{any}", .{addr2}); + defer testing.allocator.free(printed2); + const printed3 = try std.fmt.allocPrint(alloc, "{any}", .{addr3}); + defer testing.allocator.free(printed3); + + // Check the exact compressed forms we expect. + try std.testing.expectEqualStrings("[2001:db8::1:0:0:2]:0", printed1); + try std.testing.expectEqualStrings("[2001:db8::1:2]:0", printed2); + try std.testing.expectEqualStrings("[2001:db8:1:0:1::2]:0", printed3); +} + +test "parse IPv6 address, check raw bytes" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + const expected_raw: [16]u8 = .{ + 0x20, 0x01, 0x0d, 0xb8, // 2001:db8 + 0x00, 0x00, 0x00, 0x00, // :0000:0000 + 0x00, 0x01, 0x00, 0x00, // :0001:0000 + 0x00, 0x00, 0x00, 0x02, // :0000:0002 + }; + + const addr = try std.net.Address.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0); + + const actual_raw = addr.in6.sa.addr[0..]; + try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw); + +} + test "parse and render IPv6 addresses" { if (builtin.os.tag == .wasi) return error.SkipZigTest;