This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch dev-1-2-12 in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git
commit e525832f22bd2721bb7321090117d3dadc454592 Author: Alan M. Carroll <a...@apache.org> AuthorDate: Tue Oct 20 09:04:42 2020 -0500 IP: add compact range output support. --- code/include/swoc/swoc_ip.h | 38 ++++++++++++++++++++++++++++++++++++++ code/src/bw_ip_format.cc | 26 ++++++++++++++++++++++++++ code/src/swoc_ip.cc | 25 +++++++++++++++++++++++++ doc/code/IPSpace.en.rst | 14 ++++++++++++++ unit_tests/test_ip.cc | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 135 insertions(+), 1 deletion(-) diff --git a/code/include/swoc/swoc_ip.h b/code/include/swoc/swoc_ip.h index de9627d..44152c0 100644 --- a/code/include/swoc/swoc_ip.h +++ b/code/include/swoc/swoc_ip.h @@ -831,6 +831,14 @@ public: */ bool load(string_view text); + /** Compute the mask for @a this as a network. + * + * @return If @a this is a network, the mask for that network. Otherwise an invalid mask. + * + * @see IPMask::is_valid + */ + IPMask network_mask() const; + class NetSource; /** Generate a list of networks covering @a this range. @@ -874,6 +882,9 @@ public: iterator begin() const; ///< First network. iterator end() const; ///< Past last network. + /// Return @c true if there are valid networks, @c false if not. + bool empty() const; + /// @return The current network. IP4Net operator*() const; @@ -955,6 +966,14 @@ public: */ bool load(string_view text); + /** Compute the mask for @a this as a network. + * + * @return If @a this is a network, the mask for that network. Otherwise an invalid mask. + * + * @see IPMask::is_valid + */ + IPMask network_mask() const; + class NetSource; /** Generate a list of networks covering @a this range. @@ -998,6 +1017,9 @@ public: iterator begin() const; ///< First network. iterator end() const; ///< Past last network. + /// Return @c true if there are valid networks, @c false if not. + bool empty() const; + /// @return The current network. IP6Net operator*() const; @@ -1106,6 +1128,14 @@ public: IP6Range const& ip6() const { return _range._ip6; } + /** Compute the mask for @a this as a network. + * + * @return If @a this is a network, the mask for that network. Otherwise an invalid mask. + * + * @see IPMask::is_valid + */ + IPMask network_mask() const; + class NetSource; /** Generate a list of networks covering @a this range. @@ -2627,6 +2657,10 @@ inline IP4Range::NetSource::iterator IP4Range::NetSource::end() const { return self_type{range_type{}}; } +inline bool IP4Range::NetSource::empty() const { + return _range.empty(); +} + inline IPMask IP4Range::NetSource::mask() const { return IPMask{_cidr}; } inline auto IP4Range::NetSource::operator->() -> self_type * { return this; } @@ -2650,6 +2684,10 @@ inline auto IP6Range::NetSource::end() const -> iterator { return self_type{range_type{}}; } +inline bool IP6Range::NetSource::empty() const { + return _range.empty(); +} + inline IP6Net IP6Range::NetSource::operator*() const { return IP6Net{_range.min(), _mask}; } diff --git a/code/src/bw_ip_format.cc b/code/src/bw_ip_format.cc index f45c46e..2efa30d 100644 --- a/code/src/bw_ip_format.cc +++ b/code/src/bw_ip_format.cc @@ -262,6 +262,19 @@ bwformat(BufferWriter& w, Spec const& spec, IP4Range const& range) { if (range.empty()) { w.write("*-*"_tv); } else { + // Compact means output as singleton or CIDR if that's possible. + if (spec._ext.find('c') != spec._ext.npos) { + if (range.is_singleton()) { + return bwformat(w, spec, range.min()); + } + auto mask { range.network_mask() }; + if (mask.is_valid()) { + bwformat(w, spec, range.min()); + w.write('/'); + bwformat(w, bwf::Spec::DEFAULT, mask); + return w; + } + } bwformat(w, spec, range.min()); w.write('-'); bwformat(w, spec, range.max()); @@ -274,6 +287,19 @@ bwformat(BufferWriter& w, Spec const& spec, IP6Range const& range) { if (range.empty()) { w.write("*-*"_tv); } else { + // Compact means output as singleton or CIDR if that's possible. + if (spec._ext.find('c') != spec._ext.npos) { + if (range.is_singleton()) { + return bwformat(w, spec, range.min()); + } + auto mask { range.network_mask() }; + if (mask.is_valid()) { + bwformat(w, spec, range.min()); + w.write('/'); + bwformat(w, bwf::Spec::DEFAULT, mask); + return w; + } + } bwformat(w, spec, range.min()); w.write('-'); bwformat(w, spec, range.max()); diff --git a/code/src/swoc_ip.cc b/code/src/swoc_ip.cc index 10f1d3c..c971088 100644 --- a/code/src/swoc_ip.cc +++ b/code/src/swoc_ip.cc @@ -771,6 +771,14 @@ bool IP4Range::load(string_view text) { return false; } +IPMask IP4Range::network_mask() const { + NetSource nets { *this }; + if (! nets.empty() && (*nets).as_range() == *this) { + return nets->mask(); + } + return {}; // default constructed (invalid) mask. +} + IP4Range::NetSource::NetSource(IP4Range::NetSource::range_type const& range) : _range(range) { if (!_range.empty()) { this->search_wider(); @@ -926,6 +934,23 @@ bool IPRange::empty() const { return true; } +IPMask IPRange::network_mask() const { + switch (_family) { + case AF_INET: return _range._ip4.network_mask(); + case AF_INET6: return _range._ip6.network_mask(); + default: break; + } + return {}; +} + +IPMask IP6Range::network_mask() const { + NetSource nets { *this }; + if (! nets.empty() && (*nets).as_range() == *this) { + return nets->mask(); + } + return {}; // default constructed (invalid) mask. +} + IP6Range::NetSource::NetSource(IP6Range::NetSource::range_type const& range) : _range(range) { if (!_range.empty()) { this->search_wider(); diff --git a/doc/code/IPSpace.en.rst b/doc/code/IPSpace.en.rst index a2dfed9..bd4db81 100644 --- a/doc/code/IPSpace.en.rst +++ b/doc/code/IPSpace.en.rst @@ -83,6 +83,20 @@ properly formatted, otherwise the range will be default constructed to an invali also the :libswoc:`swoc::IPRange::load` method which returns a :code:`bool` to indicate if the parsing was successful. +This class has formatting support in "bwf_ip.h". In addition to all of the formatting supported for +:code:`sockaddr`, the additional extension code 'c' can be used to indicate compact range formatting. +Compact means a singleton range will be written as just the single address, and if the range is +also a network it will be printed in CIDR format. + +======================= ======================= +Range Compact +======================= ======================= +10.1.0.0-10.1.0.127 10.1.0.0/25 +10.2.0.1-10.2.0.127 10.2.0.1-10.2.0.127 +10.3.0.0-10.3.0.126 10.3.0.0-10.3.0.126 +10.4.1.1-10.4.1.1 10.4.1.1 +======================= ======================= + .. _ip-space: IPSpace diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc index aed4618..5e381b0 100644 --- a/unit_tests/test_ip.cc +++ b/unit_tests/test_ip.cc @@ -321,6 +321,37 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") { REQUIRE(w.view() == "0000:0000:0000:0000:0000:0000:0000:0001"); w.clear().print("{:: =a}", ep); REQUIRE(w.view() == " 0: 0: 0: 0: 0: 0: 0: 1"); + + std::string_view r_1{"10.1.0.0-10.1.0.127"}; + std::string_view r_2{"10.2.0.1-10.2.0.127"}; // not a network - bad start + std::string_view r_3{"10.3.0.0-10.3.0.126"}; // not a network - bad end + std::string_view r_4{"10.4.1.1-10.4.1.1"}; // singleton + + IPRange r; + + r.load(r_1); + w.clear().print("{}", r); + REQUIRE(w.view() == r_1); + w.clear().print("{::c}", r); + REQUIRE(w.view() == "10.1.0.0/25"); + + r.load(r_2); + w.clear().print("{}", r); + REQUIRE(w.view() == r_2); + w.clear().print("{::c}", r); + REQUIRE(w.view() == r_2); + + r.load(r_3); + w.clear().print("{}", r); + REQUIRE(w.view() == r_3); + w.clear().print("{::c}", r); + REQUIRE(w.view() == r_3); + + r.load(r_4); + w.clear().print("{}", r); + REQUIRE(w.view() == r_4); + w.clear().print("{::c}", r); + REQUIRE(w.view() == "10.4.1.1"); } TEST_CASE("IP ranges and networks", "[libswoc][ip][net][range]") { @@ -565,7 +596,7 @@ TEST_CASE("IP ranges and networks", "[libswoc][ip][net][range]") { ++r5_net; } - // Try it again, using @c IPRange. + // Try it again, using @c IPNet. r5_net = r_5_nets.begin(); for ( auto const&[addr, mask] : IPRange{r_5}.networks()) { REQUIRE(r5_net != r_5_nets.end());