From cbd43914a51f8b9c20133cf0b5f9204b7d6c8b7f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 13 Feb 2026 20:44:40 -0600 Subject: [PATCH] Network: Improve IPv4PortRange::ToString to support CIDR notation and only last octet difference. --- Source/Core/Common/Network.cpp | 36 +++++++++++++++++----- Source/UnitTests/Common/StringUtilTest.cpp | 36 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index 24289cc04d..d44d106da5 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -57,17 +57,39 @@ bool IPv4PortRange::IsMatch(IPv4Port subject) const std::string IPv4PortRange::ToString() const { - const std::string ip_range = first.ip_address == last.ip_address ? - Common::IPAddressToString(first.ip_address) : - fmt::format("{}-{}", Common::IPAddressToString(first.ip_address), - Common::IPAddressToString(last.ip_address)); + const u32 first_ip_value = first.GetIPAddressValue(); + const u32 last_ip_value = last.GetIPAddressValue(); + const u32 different_bits = first_ip_value ^ last_ip_value; + const u32 common_high_bits = std::countl_zero(different_bits); + + std::string ip_range = Common::IPAddressToString(first.ip_address); + if (common_high_bits == 32) + { + // Identical first and last IP. + } + else if ((last_ip_value - first_ip_value + 1) << common_high_bits == 0) + { + // An exact network range can use CIDR notation. + ip_range = fmt::format("{}/{}", ip_range, common_high_bits); + } + else if (common_high_bits >= 24) + { + // Only the last octet is different. + ip_range = fmt::format("{}-{}", ip_range, last.ip_address.back()); + } + else + { + // Plainly specified range. + ip_range = fmt::format("{}-{}", ip_range, Common::IPAddressToString(last.ip_address)); + } if (first.port == 0) return ip_range; - else if (first.port == last.port) + + if (first.port == last.port) return fmt::format("{}:{}", ip_range, first.GetPortValue()); - else - return fmt::format("{}:{}-{}", ip_range, first.GetPortValue(), last.GetPortValue()); + + return fmt::format("{}:{}-{}", ip_range, first.GetPortValue(), last.GetPortValue()); } std::string IPAddressToString(IPAddress ip_address) diff --git a/Source/UnitTests/Common/StringUtilTest.cpp b/Source/UnitTests/Common/StringUtilTest.cpp index c8bd5f4deb..f7c3b9169b 100644 --- a/Source/UnitTests/Common/StringUtilTest.cpp +++ b/Source/UnitTests/Common/StringUtilTest.cpp @@ -373,6 +373,42 @@ TEST(StringUtil, StringToIPv4PortRange) EXPECT_TRUE(parse("192.168.0.13-14:80-81")->IsMatch(parse("192.168.0.14:81")->first)); } +TEST(StringUtil, IPv4PortRangeToString) +{ + Common::IPv4PortRange subject{ + .first = {{10, 3, 0, 127}, 0}, + .last = {{10, 3, 0, 127}, 0}, + }; + + EXPECT_EQ(subject.ToString(), "10.3.0.127"); + + subject.last.port = Common::swap16(80); // First port is still zero. + EXPECT_EQ(subject.ToString(), "10.3.0.127"); + + subject.first.port = Common::swap16(80); + EXPECT_EQ(subject.ToString(), "10.3.0.127:80"); + + subject.last.port = Common::swap16(88); + EXPECT_EQ(subject.ToString(), "10.3.0.127:80-88"); + + subject.last.ip_address = {10, 3, 0, 128}; + EXPECT_EQ(subject.ToString(), "10.3.0.127-128:80-88"); + + subject.last.ip_address = {10, 3, 7, 35}; + EXPECT_EQ(subject.ToString(), "10.3.0.127-10.3.7.35:80-88"); + + subject.first.ip_address = {192, 168, 0, 0}; + subject.last.ip_address = {192, 168, 0, 255}; + EXPECT_EQ(subject.ToString(), "192.168.0.0/24:80-88"); + + subject.last.ip_address = {192, 168, 3, 255}; + EXPECT_EQ(subject.ToString(), "192.168.0.0/22:80-88"); + + subject.first.ip_address = {}; + subject.last.ip_address = {255, 255, 255, 255}; + EXPECT_EQ(subject.ToString(), "0.0.0.0/0:80-88"); +} + TEST(StringUtil, CharacterEncodingConversion) { // wstring