From c10f27fdb2d335954dd1017ce6d5800159427374 Mon Sep 17 00:00:00 2001 From: practicalswift Date: Sat, 17 Apr 2021 11:11:16 +0000 Subject: [PATCH] net: Make IPv6ToString do zero compression as described in RFC 5952 --- src/netaddress.cpp | 59 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/netaddress.cpp b/src/netaddress.cpp index d56ae78e92..cbccb0a8a9 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -556,20 +556,57 @@ static std::string IPv4ToString(Span a) return strprintf("%u.%u.%u.%u", a[0], a[1], a[2], a[3]); } +// Return an IPv6 address text representation with zero compression as described in RFC 5952 +// ("A Recommendation for IPv6 Address Text Representation"). static std::string IPv6ToString(Span a) { assert(a.size() == ADDR_IPV6_SIZE); - // clang-format off - return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", - ReadBE16(&a[0]), - ReadBE16(&a[2]), - ReadBE16(&a[4]), - ReadBE16(&a[6]), - ReadBE16(&a[8]), - ReadBE16(&a[10]), - ReadBE16(&a[12]), - ReadBE16(&a[14])); - // clang-format on + const std::array groups{ + ReadBE16(&a[0]), + ReadBE16(&a[2]), + ReadBE16(&a[4]), + ReadBE16(&a[6]), + ReadBE16(&a[8]), + ReadBE16(&a[10]), + ReadBE16(&a[12]), + ReadBE16(&a[14]), + }; + + // The zero compression implementation is inspired by Rust's std::net::Ipv6Addr, see + // https://github.com/rust-lang/rust/blob/cc4103089f40a163f6d143f06359cba7043da29b/library/std/src/net/ip.rs#L1635-L1683 + struct ZeroSpan { + size_t start_index{0}; + size_t len{0}; + }; + + // Find longest sequence of consecutive all-zero fields. Use first zero sequence if two or more + // zero sequences of equal length are found. + ZeroSpan longest, current; + for (size_t i{0}; i < groups.size(); ++i) { + if (groups[i] != 0) { + current = {i + 1, 0}; + continue; + } + current.len += 1; + if (current.len > longest.len) { + longest = current; + } + } + + std::string r; + r.reserve(39); + for (size_t i{0}; i < groups.size(); ++i) { + // Replace the longest sequence of consecutive all-zero fields with two colons ("::"). + if (longest.len >= 2 && i >= longest.start_index && i < longest.start_index + longest.len) { + if (i == longest.start_index) { + r += "::"; + } + continue; + } + r += strprintf("%s%x", ((!r.empty() && r.back() != ':') ? ":" : ""), groups[i]); + } + + return r; } std::string CNetAddr::ToStringIP() const