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());

Reply via email to