This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 61b27c3403 Update to libswoc 1.5.0 (#9830)
61b27c3403 is described below

commit 61b27c340368e51e4879f82c1b7775b5000fd79e
Author: Alan M. Carroll <a...@apache.org>
AuthorDate: Thu Jun 15 08:59:01 2023 -0500

    Update to libswoc 1.5.0 (#9830)
---
 lib/swoc/CMakeLists.txt               |   2 +-
 lib/swoc/Makefile.am                  |   2 +-
 lib/swoc/include/swoc/DiscreteRange.h | 234 ++++++++----
 lib/swoc/include/swoc/IPRange.h       | 700 ++++++++++++++++++++++++++--------
 lib/swoc/include/swoc/Lexicon.h       |   2 +-
 lib/swoc/include/swoc/MemSpan.h       |  23 +-
 lib/swoc/include/swoc/bwf_base.h      |  28 +-
 lib/swoc/include/swoc/swoc_version.h  |   6 +-
 lib/swoc/src/swoc_file.cc             |   2 +
 lib/swoc/src/swoc_ip.cc               |  96 +++--
 proxy/ControlMatcher.cc               |   2 +-
 11 files changed, 798 insertions(+), 299 deletions(-)

diff --git a/lib/swoc/CMakeLists.txt b/lib/swoc/CMakeLists.txt
index 88a549ebe8..c49f8a67fc 100644
--- a/lib/swoc/CMakeLists.txt
+++ b/lib/swoc/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.11)
 
 project(Lib-SWOC CXX)
-set(LIBSWOC_VERSION "1.4.10")
+set(LIBSWOC_VERSION "1.5.0")
 set(CMAKE_CXX_STANDARD 17)
 cmake_policy(SET CMP0087 NEW)
 # override "lib64" to be "lib" unless the user explicitly sets it.
diff --git a/lib/swoc/Makefile.am b/lib/swoc/Makefile.am
index 98d72ca31d..d5d9b9261b 100644
--- a/lib/swoc/Makefile.am
+++ b/lib/swoc/Makefile.am
@@ -22,7 +22,7 @@ library_includedir=$(includedir)/swoc
 
 AM_CPPFLAGS += @SWOC_INCLUDES@
 
-libtsswoc_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -release 1.4.10
+libtsswoc_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -release 1.5.0
 libtsswoc_la_SOURCES = \
        src/ArenaWriter.cc  src/bw_format.cc  src/bw_ip_format.cc  
src/Errata.cc  src/MemArena.cc  src/RBTree.cc  src/swoc_file.cc  src/swoc_ip.cc 
 src/TextView.cc src/string_view_util.cc
 
diff --git a/lib/swoc/include/swoc/DiscreteRange.h 
b/lib/swoc/include/swoc/DiscreteRange.h
index a04a3e7d27..ff681f2484 100644
--- a/lib/swoc/include/swoc/DiscreteRange.h
+++ b/lib/swoc/include/swoc/DiscreteRange.h
@@ -127,7 +127,7 @@ enum class DiscreteRangeEdgeRelation : uint8_t {
    - have value semantics
    - have minimum and maximum values either
      - members @c MIN and @c MAX that define static instances
-     - support @c std::numeric_limits<T>
+     - @c std::numeric_limits<T> support.
 
    The interval is always an inclusive (closed) contiguous interval,
    defined by the minimum and maximum values contained in the interval.
@@ -147,7 +147,7 @@ public:
   using EdgeRelation = DiscreteRangeEdgeRelation; ///< Import type for 
convenience.
 
   /** Default constructor.
-      An invalid (empty) range is constructed.
+   * An invalid (empty) range is constructed.
    */
   constexpr DiscreteRange() : _min(detail::maximum<T>()), 
_max(detail::minimum<T>()) {}
 
@@ -174,109 +174,139 @@ public:
    */
   bool empty() const;
 
+  /** Update the range.
+   *
+   * @param min New minimum value.
+   * @param max New maximum value.
+   * @return @a this.
+   */
   self_type &assign(metric_type const &min, metric_type const &max);
 
-  /// Set the interval to be a singleton.
-  self_type &assign(metric_type const &singleton);
+  /** Update the range.
+   *
+   * @param value The new minimum and maximum value.
+   * @return @a this.
+   *
+   * The range will contain the single value @a value.
+   */
+  self_type &assign(metric_type const &value);
 
+  /** Update the minimum value.
+   *
+   * @param min The new minimum value.
+   * @return @a this.
+   *
+   * @note No checks are done - this can result in an empty range.
+   */
   self_type &assign_min(metric_type const &min);
 
+  /** Update the maximum value.
+   *
+   * @param max The new maximum value.
+   * @return @a this.
+   *
+   * @note No checks are done - this can result in an empty range.
+   */
   self_type &assign_max(metric_type const &max);
 
   /** Decrement the maximum value.
    *
    * @return @a this.
+   *
+   * @note No checks are done, the caller must ensure it is valid to 
decremented the current maximum.
    */
-  self_type &
-  clip_max() {
-    --_max;
-    return *this;
-  }
+  self_type & clip_max();
 
-  /** Get the minimum value in the interval.
-      @note The return value is unspecified if the interval is empty.
+  /** Minimum value.
+   * @return the minimum value in the range.
+   * @note The return value is unspecified if the interval is empty.
    */
   metric_type const &min() const;
 
-  /** Get the maximum value in the interval.
-      @note The return value is unspecified if the interval is empty.
+  /** Maximum value.
+   * @return The maximum value in the range.
+   * @note The return value is unspecified if the interval is empty.
    */
   metric_type const &max() const;
 
-  /// Test for equality.
-  bool
-  operator==(self_type const &that) const {
-    return _min == that._min && _max == that._max;
-  }
+  /// Equality.
+  bool operator==(self_type const &that) const;
 
-  /// Test for inequality.
-  bool
-  operator!=(self_type const &that) const {
-    return _min != that._min | _max != that._max;
-  }
+  /// Inequality.
+  bool operator!=(self_type const &that) const;
 
   /** Check if a value is in @a this range.
    *
-   * @param m Metric value to check.
-   * @return @c true if @a m is in the range, @c false if not.
+   * @param value Metric value to check.
+   * @return @c true if @a value is in the range, @c false if not.
    */
-  bool
-  contains(metric_type const &m) const {
-    return _min <= m && m <= _max;
-  }
+  bool contains(metric_type const &value) const;
 
-  /** Logical intersection test for two intervals.
-      @return @c true if there is at least one common value in the
-      two intervals, @c false otherwise.
-  */
+  /** Logical intersection.
+   * @return @c true if there is at least one common value in the two 
intervals, @c false otherwise.
+   */
   bool has_intersection_with(self_type const &that) const;
 
   /** Compute the intersection of two intervals
-      @return The interval consisting of values that are contained by
-      both intervals. This may be the empty interval if the intervals
-      are disjoint.
-      @internal Co-variant
+   *
+   * @return The interval consisting of values that are contained by both 
intervals. The return range
+   * is empty if the intervals are disjoint.
+   *
+   * @internal Co-variant
    */
   self_type intersection(self_type const &that) const;
 
   /** Test for adjacency.
-      @return @c true if the intervals are adjacent.
-      @note Only disjoint intervals can be adjacent.
+   * @return @c true if the intervals are adjacent.
+   * @note Only disjoint intervals can be adjacent.
    */
   bool is_adjacent_to(self_type const &that) const;
 
-  /** Test for @a this being adjacent on the left of @a that.
+  /** Lower adjacency.
    *
    * @param that Range to check for adjacency.
-   * @return @c true if @a this ends exactly the value before @a that begins.
+   * @return @c true if @a this and @ta that are adjacent and @a this is less 
than @a that.
    */
   bool is_left_adjacent_to(self_type const &that) const;
 
-  //! Test if the union of two intervals is also an interval.
+  /** Valid union.
+   *
+   * @param that Range to compare.
+   *
+   * @return @a true if the hull of @a this and @a that contains only elements 
that are also in
+   * @a this or @a that.
+   */
   bool has_union(self_type const &that) const;
 
-  /** Test if an interval is a superset of or equal to another.
-      @return @c true if every value in @c that is also in @c this.
+  /** Superset test.
+   *
+   * @return @c true if every value in @c that is also in @c this.
    */
   bool is_superset_of(self_type const &that) const;
 
-  /** Test if an interval is a subset or equal to another.
-      @return @c true if every value in @c this is also in @c that.
+  /** Subset test.
+   *
+   *  @return @c true if every value in @c this is also in @c that.
    */
   bool is_subset_of(self_type const &that) const;
 
-  /** Test if an interval is a strict superset of another.
-      @return @c true if @c this is strictly a superset of @a rhs.
+  /** Strict superset test.
+   *
+   * @return @c true if @a this contains every value in @a this and @a this 
has at least one value not
+   * in @a that.
    */
   bool is_strict_superset_of(self_type const &that) const;
 
-  /** Test if an interval is a strict subset of another.
-      @return @c true if @c this is strictly a subset of @a that.
+  /** Strict subset test.
+   *
+   * @return @c true if @a that contains every value in @a this and @a that 
has at least one value not
+   * in @a this.
    */
   bool is_strict_subset_of(self_type const &that) const;
 
-  /** Determine the relationship between @c this and @a that interval.
-      @return The relationship type.
+  /** Generic relationship.
+   *
+   * @return The relationship between @a this and @a that.
    */
   Relation relationship(self_type const &that) const;
 
@@ -292,18 +322,11 @@ public:
    * - OVLP: @a that left edge is inside @a this.
    * - NONE: @a that left edge is left of @a this.
    */
-  EdgeRelation
-  left_edge_relationship(self_type const &that) const {
-    if (_max < that._max) {
-      return ++metric_type(_max) < that._max ? EdgeRelation::GAP : 
EdgeRelation::ADJ;
-    }
-    return _min >= that._min ? EdgeRelation::NONE : EdgeRelation::OVLP;
-  }
+  EdgeRelation left_edge_relationship(self_type const &that) const;
 
-  /** Compute the convex hull of this interval and another one.
-      @return The smallest interval that is a superset of @c this
-      and @a that interval.
-      @internal Co-variant
+  /** Convex hull.
+   * @return The smallest interval that is a superset of @c this and @a that.
+   * @internal Co-variant
    */
   self_type hull(self_type const &that) const;
 
@@ -313,36 +336,32 @@ public:
   /** Test for empty, operator form.
       @return @c true if the interval is empty, @c false otherwise.
    */
-  bool
-  operator!() const {
-    return _min > _max;
-  }
+  bool operator!() const;
 
   /** Test for non-empty.
    *
    * @return @c true if there values in the range, @c false if no values in 
the range.
    */
-  explicit operator bool() const { return _min <= _max; }
+  explicit operator bool() const;
 
-  /// @return @c true if the range is maximal, @c false otherwise.
+  /** Maximality.
+    * @return @c true if this range contains every value.
+   */
   bool is_maximal() const;
 
   /** Clip interval.
-      Remove all element in @c this interval not in @a that interval.
+   *  Remove all element in @c this interval not in @a that interval.
    */
   self_type &operator&=(self_type const &that);
 
   /** Convex hull.
-      Extend interval to cover all elements in @c this and @a that.
+    * Minimally extend @a this to cover all elements in @c this and @a that.
+    * @return @a this.
    */
   self_type &operator|=(self_type const &that);
 
-  self_type &
-  clear() {
-    _min = detail::maximum<T>();
-    _max = detail::minimum<T>();
-    return *this;
-  }
+  /// Make the range empty.
+  self_type & clear();
 
   /** Functor for lexicographic ordering.
       If, for some reason, an interval needs to be put in a container
@@ -368,6 +387,47 @@ public:
   };
 };
 
+template <typename T>
+bool
+DiscreteRange<T>::operator==(DiscreteRange::self_type const &that) const {
+  return _min == that._min && _max == that._max;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::operator!=(DiscreteRange::self_type const &that) const {
+  return _min != that._min | _max != that._max;
+}
+
+template <typename T>
+auto
+DiscreteRange<T>::clip_max() -> self_type & {
+  --_max;
+  return *this;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::operator!() const {
+  return _min > _max;
+}
+
+template <typename T> DiscreteRange<T>::operator bool() const { return _min <= 
_max; }
+
+template <typename T>
+bool
+DiscreteRange<T>::contains(metric_type const &value) const {
+  return _min <= value && value <= _max;
+}
+
+template <typename T>
+auto
+DiscreteRange<T>::clear() -> self_type & {
+  _min = detail::maximum<T>();
+  _max = detail::minimum<T>();
+  return *this;
+}
+
 template <typename T>
 bool
 DiscreteRange<T>::lexicographic_order::operator()(DiscreteRange::self_type 
const &lhs, DiscreteRange::self_type const &rhs) const {
@@ -390,8 +450,17 @@ DiscreteRange<T>::hull(DiscreteRange::self_type const 
&that) const {
 }
 
 template <typename T>
-typename DiscreteRange<T>::Relation
-DiscreteRange<T>::relationship(self_type const &that) const {
+auto
+DiscreteRange<T>::left_edge_relationship(self_type const &that) const -> 
EdgeRelation {
+  if (_max < that._max) {
+    return ++metric_type(_max) < that._max ? EdgeRelation::GAP : 
EdgeRelation::ADJ;
+  }
+  return _min >= that._min ? EdgeRelation::NONE : EdgeRelation::OVLP;
+}
+
+template <typename T>
+auto
+DiscreteRange<T>::relationship(self_type const &that) const -> Relation{
   Relation retval = Relation::NONE;
   if (this->has_intersection(that)) {
     if (*this == that)
@@ -534,7 +603,8 @@ 
DiscreteRange<T>::is_left_adjacent_to(DiscreteRange::self_type const &that) cons
    * T has a modulus and not depend on ++t > t always being true. However, we 
know that if t1 >
    * t0 then ++t0 > t0.
    */
-  return _max < that._min && ++metric_type(_max) == that._min;
+  metric_type max(_max); // some built in integer types don't support 
increment on rvalues.
+  return _max < that._min && ++max == that._min;
 }
 
 template <typename T>
@@ -543,10 +613,6 @@ DiscreteRange<T>::intersection(DiscreteRange::self_type 
const &that) const {
   return {std::max(_min, that._min), std::min(_max, that._max)};
 }
 
-/** Equality.
-    Two intervals are equal if their min and max values are equal.
-    @relates DiscreteRange
- */
 template <typename T>
 bool
 operator==(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
diff --git a/lib/swoc/include/swoc/IPRange.h b/lib/swoc/include/swoc/IPRange.h
index aa93d17e65..b6e68f5ce3 100644
--- a/lib/swoc/include/swoc/IPRange.h
+++ b/lib/swoc/include/swoc/IPRange.h
@@ -8,22 +8,19 @@
 
 #include <string_view>
 #include <variant> // for std::monostate
+#include <tuple>
 
 #include <swoc/DiscreteRange.h>
 #include <swoc/IPAddr.h>
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 
-using ::std::string_view;
+using std::string_view;
 
 class IP4Net;
 class IP6Net;
 class IPNet;
 
-namespace detail {
-extern void *const pseudo_nullptr;
-}
-
 /** An inclusive range of IPv4 addresses.
  */
 class IP4Range : public DiscreteRange<IP4Addr> {
@@ -33,7 +30,7 @@ class IP4Range : public DiscreteRange<IP4Addr> {
 
 public:
   /// Default constructor, invalid range.
-  IP4Range() = default;
+  constexpr IP4Range() = default;
 
   /// Construct from an network expressed as @a addr and @a mask.
   IP4Range(IP4Addr const &addr, IPMask const &mask);
@@ -47,8 +44,10 @@ public:
    * @param text Range text.
    * @see IP4Range::load
    *
-   * This results in a zero address if @a text is not a valid string. If this 
should be checked,
+   * This results in an empty range if @a text is not a valid string. If this 
should be checked,
    * use @c load.
+   *
+   * @see load
    */
   IP4Range(string_view const &text);
 
@@ -186,9 +185,9 @@ public:
   /** Construct range from @a text.
    *
    * @param text Range text.
-   * @see IP4Range::load
+   * @see IP6Range::load
    *
-   * This results in a zero address if @a text is not a valid string. If this 
should be checked,
+   * This results in an empty range if @a text is not a valid string. If this 
should be checked,
    * use @c load.
    */
   IP6Range(string_view const &text);
@@ -311,6 +310,8 @@ protected:
   bool is_valid(IPMask const &mask);
 };
 
+class IPRangeView; // Forward declare.
+
 /** Range of IP addresses.
  * Although this can hold IPv4 or IPv6, any specific instance is one or the 
other, this can never contain
  * a range of different address families.
@@ -335,6 +336,7 @@ public:
    * @param max Maximum range value.
    */
   IPRange(IP4Addr const &min, IP4Addr const &max);
+
   /** Construct an inclusive range.
    *
    * @param min Minimum range value.
@@ -346,8 +348,8 @@ public:
    *
    * @param addr Address of range.
    */
-
   IPRange(IPAddr const& addr) : IPRange(addr, addr) {}
+
   /** Construct a singleton range.
    *
    * @param addr Address of range.
@@ -366,6 +368,8 @@ public:
   /// Construct from an IPv6 @a range.
   IPRange(IP6Range const &range);
 
+  /// Construct from view.
+  IPRange(IPRangeView const& view);
 
   /** Construct from a string format.
    *
@@ -375,25 +379,35 @@ public:
    */
   IPRange(string_view const &text);
 
+  /** Update with an IPv4 range.
+   *
+   * @param min Minimum value.
+   * @param max Maximum value.
+   * @return @a this.
+   */
   self_type & assign(IP4Addr const& min, IP4Addr const& max);
+
+  /** Update with an IPv6 range.
+   *
+   * @param min Minimum address.
+   * @param max Maximum address.
+   * @return @a this
+   */
   self_type & assign(IP6Addr const& min, IP6Addr const& max);
 
+  /// Assign from view.
+  self_type & operator = (IPRangeView const& rv);
+
   /// Equality
   bool operator==(self_type const &that) const;
   /// Inequality
   bool operator!=(self_type const& that) const;
 
   /// @return @c true if this is an IPv4 range, @c false if not.
-  bool
-  is_ip4() const {
-    return AF_INET == _family;
-  }
+  bool is_ip4() const;
 
   /// @return @c true if this is an IPv6 range, @c false if not.
-  bool
-  is_ip6() const {
-    return AF_INET6 == _family;
-  }
+  bool is_ip6() const;
 
   /** Check if @a this range is the IP address @a family.
    *
@@ -421,6 +435,9 @@ public:
   /// @return @c true if there are no addresses in the range.
   bool empty() const;
 
+  /// Make the range empty / invalid.
+  self_type & clear();
+
   /// @return The IPv4 range.
   IP4Range const & ip4() const { return _range._ip4; }
 
@@ -438,6 +455,7 @@ public:
    */
   IPMask network_mask() const;
 
+  /// Container for remaining range for network generation.
   class NetSource;
 
   /** Generate a list of networks covering @a this range.
@@ -473,6 +491,7 @@ protected:
     IP4Range _ip4;       ///< IPv4 range.
     IP6Range _ip6;       ///< IPv6 range.
   } _range{std::monostate{}};
+
   /// Family of @a _range.
   sa_family_t _family{AF_UNSPEC};
 };
@@ -490,6 +509,9 @@ public:
   /// Construct from @a range.
   explicit NetSource(range_type const &range);
 
+  /// Construct from view.
+  explicit NetSource(IPRangeView const& rv);
+
   /// Copy constructor.
   NetSource(self_type const &that) = default;
 
@@ -533,9 +555,98 @@ protected:
     IP4Range::NetSource _ip4; ///< IPv4 addresses.
     IP6Range::NetSource _ip6; ///< IPv6 addresses.
   };
+
   sa_family_t _family = AF_UNSPEC; ///< Mark for union content.
 };
 
+/** View of a range.
+ *
+ * The point of this is @c IPRange is really a union on top of @c IP4Range and 
@c IP6Range therefore
+ * using it requires copying. A view enable using an IPv4 or IPv6 range as a 
generic range without
+ * the copy. This is useful in situations where performance is critical.
+ *
+ * Used primarily for iterator implementation where the ranges are stored in 
family specific ranges
+ * in the container. The iterator can use this to provide access as an @c 
IPRange without a copying
+ * every iteration.
+ *
+ * @note An earlier implementation used references and updated those to 
achieve this same effect but
+ * that appeared to confusion the optimizer and odd effects would happen at 
higher optimization levels
+ * (in particular, iterators would appear to not actually increment even 
thought the increment code
+ * was invoked).
+ */
+class IPRangeView {
+  using self_type = IPRangeView;
+  friend IPRange;
+  /// Storage for the view pointer - union because only one can be valid.
+  using storage_type = union {
+    std::monostate _nil; ///< No data present.
+    IP4Range const * _4; ///< IPv4 range.
+    IP6Range const * _6; ///< IPv6 range.
+  };
+public:
+  /// Default constructor - invalid view.
+  IPRangeView() = default;
+
+  /// Compare to a range.
+  bool operator == (IPRange const& r) const;
+
+  /// @return @c true if there is no valid view to a range.
+  bool valid() const;
+
+  // @return @c true if the range is empty (invalid views are empty)
+  bool empty() const;
+
+  /// Reset to invalid view.
+  self_type & clear();
+
+  /// Update the view.
+  self_type & assign(IP4Range const& r);
+
+  /// Update the view.
+  self_type & assign(IP6Range const& r);
+
+  /// @return @c true if this is an IPv4 range.
+  bool is_ip4() const;
+
+  /// @return @c true if this is an IPv6 range.
+  bool is_ip6() const;
+
+  /// @return Reference to the viewed IPv4 range.
+  IP4Range const& ip4() const;
+
+  /// @return Reference to the viewd IPv6 range.
+  IP6Range const& ip6() const;
+
+  /// @{
+  /// Forwarding methods.
+
+  /// @return The minimum address in the range.
+  IPAddr min() const;
+
+  /// @return The maximum value in the range.
+  IPAddr max() const;
+
+  /// Equality.
+  bool operator == (self_type const& that) const;
+
+  /// Inequality.
+  bool operator != (self_type const& that) const;
+
+  /** Generate a list of networks covering @a this range.
+   *
+   * @return A network generator.
+   *
+   * @see IPRange::networks
+   */
+  IPRange::NetSource networks() const { return IPRange::NetSource(*this); }
+
+  /// @}
+
+// protected:
+  storage_type _raw = { std::monostate() }; ///< Storage for the view pointer.
+  sa_family_t _family = AF_UNSPEC; ///< Range address family.
+};
+
 /// An IPv4 network.
 class IP4Net {
   using self_type = IP4Net; ///< Self reference type.
@@ -563,8 +674,8 @@ public:
    */
   bool load(swoc::TextView text);
 
-  /// @return @c true if the network is valid, @c false if not.
-  bool is_valid() const;
+  /// @return @c true if the network contains no addresses (is invalid).
+  bool empty() const;
 
   /// @return Network address - smallest address in the network.
   IP4Addr min() const;
@@ -572,12 +683,6 @@ public:
   /// @return The largest address in the network.
   IP4Addr max() const;
 
-  /// @return Network address.
-  [[deprecated]] IP4Addr lower_bound() const;
-
-  /// @return The largest address in the network.
-  [[deprecated]] IP4Addr upper_bound() const;
-
   /// @return The mask for the network.
   IPMask const &mask() const;
 
@@ -624,6 +729,15 @@ public:
    */
   IP6Net(IP6Addr addr, IPMask mask);
 
+  /** Construct from text.
+   *
+   * @param text Network description.
+   *
+   * The format must be "addr/mask" where "addr" is a valid address and mask 
is either a single
+   * number for the mask width (CIDR) or a mask in address notation.
+   */
+  IP6Net(TextView text) { this->load(text); }
+
   /** Parse network as @a text.
    *
    * @param text String describing the network in CIDR format.
@@ -631,8 +745,8 @@ public:
    */
   bool load(swoc::TextView text);
 
-  /// @return @c true if the network is valid, @c false if not.
-  bool is_valid() const;
+  /// @return @c true if the network contains no addresses (is invalid).
+  bool empty() const;
 
   /// @return Network address - smallest address in the network.
   IP6Addr min() const;
@@ -640,12 +754,6 @@ public:
   /// @return Largest address in the network.
   IP6Addr max() const;
 
-  /// @return THh smallest address in the network.
-  [[deprecated]] IP6Addr lower_bound() const;
-
-  /// @return The largest address in the network.
-  [[deprecated]] IP6Addr upper_bound() const;
-
   /// @return The mask for the network.
   IPMask const &mask() const;
 
@@ -698,6 +806,13 @@ public:
    */
   IPNet(IPAddr const &addr, IPMask const &mask);
 
+  /** Construct from text.
+   *
+   * @param text Network description.
+   *
+   * The format must be "addr/mask" where "addr" is a valid address and mask 
is either a single
+   * number for the mask width (CIDR) or a mask in address notation.
+   */
   IPNet(TextView text);
 
   /** Parse network as @a text.
@@ -707,8 +822,8 @@ public:
    */
   bool load(swoc::TextView text);
 
-  /// @return @c true if the network is valid, @c false if not.
-  bool is_valid() const;
+  /// @return @c true if the network contains no addresses (is invalid).
+  bool empty() const;
 
   /// @return Network address - smallest address in the network.
   IPAddr min() const;
@@ -716,12 +831,7 @@ public:
   /// @return Largest address in the network.
   IPAddr max() const;
 
-  /// @return THh smallest address in the network.
-  [[deprecated]] IPAddr lower_bound() const;
-
-  /// @return The largest address in the network.
-  [[deprecated]] IPAddr upper_bound() const;
-
+  /// @return The number of bits in the mask.
   IPMask::raw_type width() const;
 
   /// @return The mask for the network.
@@ -730,21 +840,20 @@ public:
   /// @return A range that exactly covers the network.
   IPRange as_range() const;
 
+  /// @return @c true if network is IPv4.
   bool is_ip4() const { return _addr.is_ip4(); }
 
+  /// @return @c true if network is IPv6.
   bool is_ip6() const { return _addr.is_ip6(); }
 
+  /// Address family of network.
   sa_family_t family() const { return _addr.family(); }
 
-  IP4Net
-  ip4() const {
-    return IP4Net{_addr.ip4(), _mask};
-  }
+  /// @return The network as explicitly IPv4.
+  IP4Net ip4() const;
 
-  IP6Net
-  ip6() const {
-    return IP6Net{_addr.ip6(), _mask};
-  }
+  /// @return The network as explicitly IPv6.
+  IP6Net ip6() const;
 
   /** Assign an @a addr and @a mask to @a this.
    *
@@ -755,11 +864,7 @@ public:
   self_type &assign(IPAddr const &addr, IPMask const &mask);
 
   /// Reset network to invalid state.
-  self_type &
-  clear() {
-    _mask.clear();
-    return *this;
-  }
+  self_type & clear();
 
   /// Equality.
   bool operator==(self_type const &that) const;
@@ -773,6 +878,135 @@ protected:
 };
 
 // --------------------------------------------------------------------------
+namespace detail {
+/** Value type for @c IPSpace constant iterator.
+ *
+ * @tparam PAYLOAD User data type to be stored.
+ *
+ * @internal Exported because inner classes in template classes cannot be used 
in partial
+ * specialization which is required for tuple support.
+ *
+ * @internal The value type must be split in two to provide @c const 
consistency. The structured
+ * bidingin types are determined only by the type of the value, therefore if 
the structured binding
+ * should vary in whether the payload binding is @c const then there must be 
two types. I tested using
+ * @c const in the typle type templates but the binding always used the @c 
const variant regardless of
+ * the @const state of the payload instance.
+ *
+ * @note The @c assign methods are used to update iterators.
+ *
+ * @see IPSpace
+ */
+template < typename PAYLOAD > struct ip_space_const_value_type {
+  using self_type = ip_space_const_value_type;
+
+  IPRangeView _rv; ///< View to the current range.
+  PAYLOAD * _payload = nullptr; ///< Payload for @a _range.
+
+  ip_space_const_value_type() = default;
+
+  /// Reset to default constructed state.
+  self_type & clear();
+
+  /** Update the internal state.
+   *
+   * @param r Range.
+   * @param payload Payload.
+   * @return @a this.
+   */
+  self_type & assign(IP4Range const& r, PAYLOAD & payload);
+
+  /** Update the internal state.
+   *
+   * @param r Range.
+   * @param payload Payload.
+   * @return @a this.
+   */
+  self_type & assign(IP6Range const& r, PAYLOAD & payload);
+
+  /** Update from another instance.
+   *
+   * @param that Other instance.
+   * @return @a this.
+   */
+  self_type & assign(self_type const& that);
+
+  /// Support assignemnt to @c std::tie.
+  std::tuple<IPRangeView, PAYLOAD &> tuple() const { return { _rv, *_payload 
}; }
+
+  /// Equality against an equivalent tuple.
+  bool operator == (std::tuple<swoc::IPRange, PAYLOAD> const& t) const;
+
+  /// @return The address range.
+  IPRange range() const { return _rv; }
+
+  /// @return The range view.
+  IPRangeView range_view() const { return _rv; }
+
+  /// @return A reference to the payload (user content).
+  PAYLOAD const & payload() const { return *_payload; }
+};
+
+/** Value type for @c IPSpace.
+ *
+ * @tparam PAYLOAD User data type to be stored.
+ *
+ * @internal Exported because inner classes in template classes cannot be used 
in partial
+ * specialization which is required for tuple support. This should be removed 
next API incompatible
+ * change release because tuple access is being deprecated.
+ *
+ * @note The @c assign methods are used to update iterators.
+ *
+ * @see IPSpace
+ */
+template < typename PAYLOAD > struct ip_space_value_type : 
ip_space_const_value_type<PAYLOAD> {
+  using self_type = ip_space_value_type;
+  using super_type = ip_space_const_value_type<PAYLOAD>;
+
+  /// @return A reference to the payload (user content).
+  PAYLOAD & payload() { return *super_type::_payload; }
+
+};
+
+template <typename PAYLOAD>
+auto
+ip_space_const_value_type<PAYLOAD>::clear() -> self_type & {
+  _rv.clear();
+  _payload = nullptr;
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+ip_space_const_value_type<PAYLOAD>::assign(IP4Range const &r, PAYLOAD 
&payload) -> self_type &{
+  _rv.assign(r);
+  _payload = &payload;
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+ip_space_const_value_type<PAYLOAD>::assign(IP6Range const &r, PAYLOAD 
&payload) -> self_type & {
+  _rv.assign(r);
+  _payload = &payload;
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+ip_space_const_value_type<PAYLOAD>::assign(ip_space_const_value_type::self_type
 const &that) -> self_type & {
+  _rv = that._rv;
+  _payload = that._payload;
+  return *this;
+}
+
+template <typename PAYLOAD>
+bool
+ip_space_const_value_type<PAYLOAD>::operator==(std::tuple<swoc::IPRange, 
PAYLOAD> const &t) const {
+  return _rv == std::get<0>(t) && std::get<1>(t) == *_payload;
+}
+
+} // namespace detail
+
 /** Coloring of IP address space.
  *
  * @tparam PAYLOAD The color class.
@@ -793,7 +1027,8 @@ template <typename PAYLOAD> class IPSpace {
 
 public:
   using payload_t  = PAYLOAD; ///< Export payload type.
-  using value_type = std::tuple<IPRange const, PAYLOAD &>;
+  /// Iterator value, a range and payload.
+  using value_type = detail::ip_space_value_type<PAYLOAD>;
 
   /// Construct an empty space.
   IPSpace() = default;
@@ -850,8 +1085,7 @@ public:
     self_type & blend(IP4Range const &range, U const &color, F &&blender);
 
   template <typename F, typename U = PAYLOAD>
-    self_type &
-    blend(IP6Range const &range, U const &color, F &&blender);
+    self_type & blend(IP6Range const &range, U const &color, F &&blender);
 
   /// @return The number of distinct ranges.
   size_t count() const;
@@ -898,11 +1132,11 @@ public:
     friend class IPSpace;
 
   public:
-    using value_type = std::tuple<IPRange const, PAYLOAD const &>; /// Import 
for API compliance.
+    using value_type = detail::ip_space_const_value_type<PAYLOAD>; /// Import 
for API compliance.
     // STL algorithm compliance.
     using iterator_category = std::bidirectional_iterator_tag;
-    using pointer           = value_type *;
-    using reference         = value_type &;
+    using pointer           = value_type const *;
+    using reference         = value_type const &;
     using difference_type   = int;
 
     /// Default constructor.
@@ -936,31 +1170,11 @@ public:
 
     /// Dereference.
     /// @return A reference to the referent.
-    value_type operator*() const;
+    reference operator*() const;
 
     /// Dereference.
     /// @return A pointer to the referent.
-    value_type const *operator->() const;
-
-    /** The range for the iterator.
-     *
-     * @return Iterator range.
-     *
-     * @note If the iterator is not valid the returned range will be empty.
-     */
-    IPRange const& range() const;
-
-    /** The payload for the iterator.
-     *
-     * @return The payload.
-     *
-     * @note This yields undetermined results for invalid iterators. Always 
check for validity befure
-     * using this method.
-     *
-     * @note It is not possible to retrieve a modifiable payload because that 
can break the internal
-     * invariant that adjcent ranges always have different payloads.
-     */
-    PAYLOAD const& payload() const;
+    pointer operator->() const;
 
     /// Equality
     bool operator==(self_type const &that) const;
@@ -977,7 +1191,7 @@ public:
     typename IP4Space::iterator _iter_4; ///< IPv4 sub-space iterator.
     typename IP6Space::iterator _iter_6; ///< IPv6 sub-space iterator.
     /// Current value.
-    value_type _value{IPRange{}, *static_cast<PAYLOAD 
*>(detail::pseudo_nullptr)};
+    value_type _value;
 
     /** Internal constructor.
      *
@@ -987,6 +1201,14 @@ public:
      * In practice, at most one iterator should be "internal", the other 
should be the beginning or end.
      */
     const_iterator(typename IP4Space::iterator const &iter4, typename 
IP6Space::iterator const &iter6);
+
+    // These are required because the actual range type is @c 
DiescreteRange<T> and returning as a
+    // range class creates a temporary which in turns causes some nasty side 
effects.
+
+    /// @return Properly type cast range from iterator.
+    IP4Range const& r4() { return static_cast<IP4Range 
const&>(_iter_4->range()); }
+    /// @return Properly type cast range from iterator.
+    IP6Range const& r6() { return static_cast<IP6Range 
const&>(_iter_6->range()); }
   };
 
   /** Iterator.
@@ -1008,7 +1230,7 @@ public:
     iterator(const_iterator const& that) : const_iterator(that) {}
   public:
     /// Value type of iteration.
-    using value_type = std::tuple<IPRange const, PAYLOAD &>;
+    using value_type = detail::ip_space_value_type<PAYLOAD>;
     using pointer    = value_type *;
     using reference  = value_type &;
 
@@ -1034,31 +1256,24 @@ public:
     /// Post-increment.
     /// Move to the next element in the list.
     /// @return The iterator value before the increment.
-    self_type
-    operator++(int) {
-      self_type zret{*this};
-      ++*this;
-      return zret;
-    }
+    self_type operator++(int);
 
     /// Post-decrement.
     /// Move to the previous element in the list.
     /// @return The iterator value before the decrement.
-    self_type
-    operator--(int) {
-      self_type zret{*this};
-      --*this;
-      return zret;
-    }
+    self_type operator--(int);
 
     /// Dereference.
     /// @return A reference to the referent.
-    value_type operator*() const;
+    reference operator*() const;
 
     /// Dereference.
     /// @return A pointer to the referent.
-    value_type const *operator->() const;
+    pointer operator->() const;
 
+  protected:
+    // Retrieve the value and convert from the super (const) type value to the 
value for iterator.
+    value_type & value() const;
   };
 
   /** Find the payload for an @a addr.
@@ -1254,12 +1469,11 @@ public:
     friend class IPRangeSet;
 
   public:
-    using value_type = IPRange const;
+    using value_type = IPRangeView;
     // STL algorithm compliance.
     using iterator_category = std::bidirectional_iterator_tag;
-    using pointer           = value_type *;
-    using reference         = value_type &;
-    using const_reference   = value_type const &;
+    using pointer           = value_type const *;
+    using reference         = value_type const &;
     using difference_type   = int;
 
     /// Default constructor.
@@ -1293,11 +1507,11 @@ public:
 
     /// Dereference.
     /// @return A reference to the referent.
-    value_type const& operator*() const;
+    reference operator*() const;
 
     /// Dereference.
     /// @return A pointer to the referent.
-    value_type const *operator->() const;
+    pointer operator->() const;
 
     /// Equality
     bool operator==(self_type const &that) const;
@@ -1309,6 +1523,7 @@ public:
     const_iterator(super_type const& spot) : _iter(spot) {}
 
     super_type _iter; ///< Underlying iterator.
+    IPRange _r; ///< Some less temporary storage for dereferences.
   };
 
   using iterator = const_iterator;
@@ -1376,9 +1591,9 @@ template <typename PAYLOAD>
 IPSpace<PAYLOAD>::const_iterator::const_iterator(typename IP4Space::iterator 
const &iter4, typename IP6Space::iterator const &iter6)
 : _iter_4(iter4), _iter_6(iter6) {
   if (_iter_4.has_next()) {
-    new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
+    _value.assign(this->r4(), _iter_4->payload());
   } else if (_iter_6.has_next()) {
-    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+    _value.assign(this->r6(), _iter_6->payload());
   }
 }
 
@@ -1387,7 +1602,7 @@ auto
 IPSpace<PAYLOAD>::const_iterator::operator=(self_type const &that) -> 
self_type & {
   _iter_4 = that._iter_4;
   _iter_6 = that._iter_6;
-  new (&_value) value_type{that._value};
+  _value.assign(that._value);
   return *this;
 }
 
@@ -1399,18 +1614,18 @@ IPSpace<PAYLOAD>::const_iterator::operator++() -> 
self_type & {
     ++_iter_4;
     incr_p = true;
     if (_iter_4.has_next()) {
-      new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
+      _value.assign(this->r4(), _iter_4->payload());
       return *this;
     }
   }
 
   if (_iter_6.has_next()) {
     if (incr_p || (++_iter_6).has_next()) {
-      new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+      _value.assign(this->r6(), _iter_6->payload());
       return *this;
     }
   }
-  new (&_value) value_type{IPRange{}, *static_cast<PAYLOAD 
*>(detail::pseudo_nullptr)};
+  _value.clear();
   return *this;
 }
 
@@ -1427,15 +1642,15 @@ auto
 IPSpace<PAYLOAD>::const_iterator::operator--() -> self_type & {
   if (_iter_6.has_prev()) {
     --_iter_6;
-    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+    _value.assign(this->r6(), _iter_6->payload());
     return *this;
   }
   if (_iter_4.has_prev()) {
     --_iter_4;
-    new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
+    _value.assign(this->r4(), _iter_4->payload());
     return *this;
   }
-  new (&_value) value_type{IPRange{}, *static_cast<PAYLOAD 
*>(detail::pseudo_nullptr)};
+  _value.clear();
   return *this;
 }
 
@@ -1449,24 +1664,16 @@ IPSpace<PAYLOAD>::const_iterator::operator--(int) -> 
self_type {
 
 template <typename PAYLOAD>
 auto
-IPSpace<PAYLOAD>::const_iterator::operator*() const -> value_type {
+IPSpace<PAYLOAD>::const_iterator::operator*() const -> reference {
   return _value;
 }
 
 template <typename PAYLOAD>
 auto
-IPSpace<PAYLOAD>::const_iterator::operator->() const -> value_type const * {
+IPSpace<PAYLOAD>::const_iterator::operator->() const -> pointer {
   return &_value;
 }
 
-template <typename PAYLOAD>
-IPRange const &
-IPSpace<PAYLOAD>::const_iterator::range() const { return std::get<0>(_value); }
-
-template <typename PAYLOAD>
-PAYLOAD const &
-IPSpace<PAYLOAD>::const_iterator::payload() const { return 
std::get<1>(_value); }
-
 /* Bit of subtlety with equality - although it seems that if @a _iter_4 is 
valid, it doesn't matter
  * where @a _iter6 is (because it is really the iterator location that's being 
checked), it's
  * necessary to do the @a _iter_4 validity on both iterators to avoid the case 
of a false positive
@@ -1496,14 +1703,20 @@ IPSpace<PAYLOAD>::iterator::operator=(self_type const 
&that) -> self_type & {
 
 template <typename PAYLOAD>
 auto
-IPSpace<PAYLOAD>::iterator::operator->() const -> value_type const * {
-  return static_cast<value_type *>(&super_type::_value);
+IPSpace<PAYLOAD>::iterator::value() const -> value_type & {
+  return reinterpret_cast<value_type&>(const_cast<self_type*>(this)->_value);
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator->() const -> pointer {
+  return &(this->value());
 }
 
 template <typename PAYLOAD>
 auto
-IPSpace<PAYLOAD>::iterator::operator*() const -> value_type {
-  return reinterpret_cast<value_type const &>(super_type::_value);
+IPSpace<PAYLOAD>::iterator::operator*() const -> reference {
+  return this->value();
 }
 
 template <typename PAYLOAD>
@@ -1513,6 +1726,14 @@ IPSpace<PAYLOAD>::iterator::operator++() -> self_type & {
   return *this;
 }
 
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator++(int) -> self_type {
+  self_type zret{*this};
+  ++*this;
+  return zret;
+}
+
 template <typename PAYLOAD>
 auto
 IPSpace<PAYLOAD>::iterator::operator--() -> self_type & {
@@ -1520,7 +1741,14 @@ IPSpace<PAYLOAD>::iterator::operator--() -> self_type & {
   return *this;
 }
 
-// --------------------------------------------------------------------------
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator--(int) -> self_type {
+  self_type zret{*this};
+  --*this;
+  return zret;
+}
+
 /// 
------------------------------------------------------------------------------------
 
 // +++ IPRange +++
@@ -1563,6 +1791,14 @@ inline IPRange::IPRange(string_view const &text) {
   this->load(text);
 }
 
+inline IPRange::IPRange(IPRangeView const& view) {
+  if (AF_INET == view._family) {
+    *this = *view._raw._4;
+  } else if (AF_INET6 == _family) {
+    *this = *view._raw._6;
+  }
+}
+
 inline auto
 IPRange::assign(IP4Addr const &min, IP4Addr const &max) -> self_type & {
   _range._ip4.assign(min, max);
@@ -1577,6 +1813,22 @@ IPRange::assign(IP6Addr const &min, IP6Addr const &max) 
-> self_type & {
   return *this;
 }
 
+inline auto
+IPRange::operator=(const IPRangeView &rv) -> self_type & {
+  if (AF_INET == rv._family) {
+    *this = *rv._raw._4;
+  } else if (AF_INET6 == rv._family) {
+    *this = *rv._raw._6;
+  }
+  return *this;
+}
+
+inline auto
+IPRange::clear() -> self_type & {
+  _family = AF_UNSPEC;
+  return *this;
+}
+
 inline auto
 IPRange::networks() const -> NetSource {
   return {NetSource{*this}};
@@ -1592,6 +1844,68 @@ IPRange::operator!=(const self_type &that) const {
   return ! (*this == that);
 }
 
+inline bool
+IPRange::is_ip4() const {
+  return AF_INET == _family;
+}
+
+inline bool IPRange::is_ip6() const {
+  return AF_INET6 == _family;
+}
+
+inline auto
+IPRangeView::clear() -> self_type & {
+  _family = AF_UNSPEC;
+  return *this;
+}
+
+inline bool
+IPRangeView::empty() const {
+  return AF_INET6 == _family ? _raw._6->empty()
+         : AF_INET == _family ? _raw._4->empty()
+                               : true;
+}
+
+inline bool IPRangeView::is_ip4() const { return AF_INET == _family; }
+
+inline bool IPRangeView::is_ip6() const { return AF_INET6 == _family; }
+
+inline IP4Range const & IPRangeView::ip4() const { return *_raw._4; }
+
+inline IP6Range const & IPRangeView::ip6() const { return *_raw._6; }
+
+inline bool
+IPRangeView::valid() const { return AF_INET == _family || AF_INET6 == _family; 
}
+
+inline auto
+IPRangeView::assign(IP4Range const &r) -> self_type & {
+  _family = r.family();
+  _raw._4 = &r;
+  return *this;
+}
+
+inline auto
+IPRangeView::assign(IP6Range const &r) -> self_type & {
+  _family = r.family();
+  _raw._6 = &r;
+  return *this;
+}
+
+inline bool
+IPRangeView::operator!=(IPRangeView::self_type const &that) const {
+  return ! (*this == that);
+}
+
+inline IPAddr
+IPRangeView::min() const {
+  return AF_INET == _family ? _raw._4->min() : AF_INET6 == _family ? 
_raw._6->min() : IPAddr::INVALID;
+}
+
+inline IPAddr
+IPRangeView::max() const {
+  return AF_INET == _family ? _raw._4->max() : AF_INET6 == _family ? 
_raw._6->max() : IPAddr::INVALID;
+}
+
 // +++ IPNet +++
 
 inline IP4Net::IP4Net(swoc::IP4Addr addr, swoc::IPMask mask) : _addr(addr & 
mask), _mask(mask) {}
@@ -1602,8 +1916,8 @@ IP4Net::mask() const {
 }
 
 inline bool
-IP4Net::is_valid() const {
-  return _mask.is_valid();
+IP4Net::empty() const {
+  return ! _mask.is_valid();
 }
 
 inline IP4Addr
@@ -1616,16 +1930,6 @@ IP4Net::max() const {
   return _addr | _mask;
 }
 
-inline IP4Addr
-IP4Net::lower_bound() const {
-  return this->min();
-}
-
-inline IP4Addr
-IP4Net::upper_bound() const {
-  return this->max();
-}
-
 inline IP4Range
 IP4Net::as_range() const {
   return {this->min(), this->max()};
@@ -1656,8 +1960,8 @@ IP6Net::mask() const {
 }
 
 inline bool
-IP6Net::is_valid() const {
-  return _mask.is_valid();
+IP6Net::empty() const {
+  return ! _mask.is_valid();
 }
 
 inline IP6Addr
@@ -1670,9 +1974,6 @@ IP6Net::max() const {
   return _addr | _mask;
 }
 
-inline IP6Addr IP6Net::lower_bound() const { return this->min(); }
-inline IP6Addr IP6Net::upper_bound() const { return this->max(); }
-
 inline IP6Range
 IP6Net::as_range() const {
   return {this->min(), this->max()};
@@ -1702,8 +2003,8 @@ inline IPNet::IPNet(TextView text) {
 }
 
 inline bool
-IPNet::is_valid() const {
-  return _mask.is_valid();
+IPNet::empty() const {
+  return ! _mask.is_valid();
 }
 
 inline IPAddr
@@ -1716,9 +2017,6 @@ IPNet::max() const {
   return _addr | _mask;
 }
 
-inline IPAddr IPNet::lower_bound() const { return this->min(); }
-inline IPAddr IPNet::upper_bound() const { return this->max(); }
-
 inline IPMask::raw_type
 IPNet::width() const {
   return _mask.width();
@@ -1771,6 +2069,22 @@ operator==(IP6Net const &lhs, IPNet const &rhs) {
   return rhs.is_ip6() && rhs.ip6() == lhs;
 }
 
+inline IP4Net
+IPNet::ip4() const {
+  return IP4Net{_addr.ip4(), _mask};
+}
+
+inline IP6Net
+IPNet::ip6() const {
+  return IP6Net{_addr.ip6(), _mask};
+}
+
+inline auto
+IPNet::clear() -> self_type & {
+  _mask.clear();
+  return *this;
+}
+
 // +++ Range -> Network classes +++
 
 inline bool
@@ -1883,6 +2197,16 @@ inline 
IPRange::NetSource::NetSource(IPRange::NetSource::range_type const &range
   }
 }
 
+inline IPRange::NetSource::NetSource(IPRangeView const &rv) {
+  if (rv.is_ip4()) {
+    new (&_ip4) decltype(_ip4)(rv.ip4());
+    _family = AF_INET;
+  } else if (rv.is_ip6()) {
+    new (&_ip6) decltype(_ip6)(rv.ip6());
+    _family = AF_INET6;
+  }
+}
+
 inline auto
 IPRange::NetSource::begin() const -> iterator {
   return *this;
@@ -2179,13 +2503,13 @@ IPRangeSet::const_iterator::operator--(int) -> 
self_type {
 }
 
 inline auto
-IPRangeSet::const_iterator::operator*() const -> value_type const& {
-  return _iter.range();
+IPRangeSet::const_iterator::operator*() const -> reference {
+  return _iter->_rv;
 }
 
 inline auto
-IPRangeSet::const_iterator::operator->() const -> value_type const * {
-  return &(_iter.range());
+IPRangeSet::const_iterator::operator->() const -> pointer {
+  return &(_iter->_rv);
 }
 
 inline bool
@@ -2284,3 +2608,69 @@ get(swoc::IPNet const &net) {
 /// @endcond
 
 }} // namespace swoc::SWOC_VERSION_NS
+
+// Tuple support for IPSpace values.
+/// @cond NOT_DOCUMENTED
+
+namespace std {
+
+template <typename P> class tuple_size<swoc::detail::ip_space_value_type<P>> : 
public integral_constant<size_t, 2> {};
+template <typename P> class 
tuple_size<swoc::detail::ip_space_const_value_type<P>> : public 
integral_constant<size_t, 2> {};
+
+template <size_t IDX, typename P> class tuple_element<IDX, 
swoc::detail::ip_space_value_type<P>> {
+  static_assert("swoc::IPSpace::value_type tuple index out of range");
+};
+template <size_t IDX, typename P> class tuple_element<IDX, 
swoc::detail::ip_space_const_value_type<P>> {
+  static_assert("swoc::IPSpace::value_type tuple index out of range");
+};
+
+template <typename P> class tuple_element<0, 
swoc::detail::ip_space_value_type<P>> {
+public:
+  using type = swoc::IPRangeView;
+};
+
+template <typename P> class tuple_element<1, 
swoc::detail::ip_space_value_type<P>> {
+public:
+  using type = P &;
+};
+
+template <typename P> class tuple_element<0, 
swoc::detail::ip_space_const_value_type<P>> {
+public:
+  using type = swoc::IPRangeView;
+};
+
+template <typename P> class tuple_element<1, 
swoc::detail::ip_space_const_value_type<P>> {
+public:
+  using type = P const &;
+};
+
+} // namespace std
+
+namespace swoc { inline namespace SWOC_VERSION_NS { namespace detail {
+template <size_t IDX, typename P>
+auto
+get(ip_space_const_value_type<P> const& p) -> typename std::tuple_element<IDX, 
ip_space_const_value_type<P>>::type {
+  if constexpr (IDX == 0) {
+    return p._rv;
+  } else if constexpr (IDX == 1) {
+    return *(p._payload);
+  }
+}
+
+template <size_t IDX, typename P>
+auto
+get(ip_space_value_type<P> const& p) -> typename std::tuple_element<IDX, 
ip_space_value_type<P>>::type {
+  if constexpr (IDX == 0) {
+    return p._rv;
+  } else if constexpr (IDX == 1) {
+    return *(p._payload);
+  }
+}
+}}} // namespace swoc::detail
+
+// Support unqualified @c get because ADL doesn't seem to work.
+using swoc::detail::get;
+// Support @c std::get
+namespace std { using swoc::detail::get; }
+
+/// @endcond
diff --git a/lib/swoc/include/swoc/Lexicon.h b/lib/swoc/include/swoc/Lexicon.h
index c81337f633..166cb6f21a 100644
--- a/lib/swoc/include/swoc/Lexicon.h
+++ b/lib/swoc/include/swoc/Lexicon.h
@@ -39,7 +39,7 @@ what(std::string_view const &fmt, Args &&...args) {
 }
 
 // Exported because inner classes in template classes cannot be used in 
partial specialization
-// which is required for tuple support. This should be remove next time there 
is a API changing
+// which is required for tuple support. This should be removed next time there 
is a API changing
 // release because tuple access is being deprecated.
 template < typename E > struct lexicon_pair_type {
   E _value;
diff --git a/lib/swoc/include/swoc/MemSpan.h b/lib/swoc/include/swoc/MemSpan.h
index 8b71e0cc02..d586493fe0 100644
--- a/lib/swoc/include/swoc/MemSpan.h
+++ b/lib/swoc/include/swoc/MemSpan.h
@@ -354,12 +354,6 @@ public:
   /// Destruct all elements in the span.
   void destroy();
 
-  /** Return a view of the memory.
-   *
-   * @return A @c string_view covering the span contents.
-   */
-  [[deprecated]] std::string_view view() const;
-
   template <typename U> friend class MemSpan;
 };
 
@@ -616,12 +610,6 @@ public:
    */
   self_type align(size_t alignment, size_t obj_size) const;
 
-  /** Return a view of the memory.
-   *
-   * @return A @c string_view covering the span contents.
-   */
-  [[deprecated]] std::string_view view() const;
-
 };
 
 template <> class MemSpan<void> : public MemSpan<void const> {
@@ -1231,11 +1219,6 @@ MemSpan<T>::destroy() {
   }
 }
 
-template <typename T>
-std::string_view
-MemSpan<T>::view() const {
-  return {reinterpret_cast<const char *>(_ptr), this->data_size()};
-}
 // --- void specializations ---
 
 template <typename U> constexpr MemSpan<void const>::MemSpan(MemSpan<U> const 
&that) : _ptr(const_cast<std::remove_const_t<U> *>(that._ptr)), _size(sizeof(U) 
* that.size()) {}
@@ -1533,13 +1516,11 @@ MemSpan<void const>::as_ptr() const {
   return static_cast<U const *>(_ptr);
 }
 
-inline std::string_view
-MemSpan<void const>::view() const {
-  return {static_cast<char const *>(_ptr), _size};
-}
 /// Deduction guides
 template<typename T, size_t N> MemSpan(std::array<T,N> &) -> MemSpan<T>;
 template<typename T, size_t N> MemSpan(std::array<T,N> const &) -> MemSpan<T 
const>;
+template<size_t N> MemSpan(char (&)[N]) -> MemSpan<char>;
+template<size_t N> MemSpan(char const (&)[N]) -> MemSpan<char const>;
 template<typename T> MemSpan(std::vector<T> &) -> MemSpan<T>;
 template<typename T> MemSpan(std::vector<T> const&) -> MemSpan<T const>;
 MemSpan(std::string_view const&) -> MemSpan<char const>;
diff --git a/lib/swoc/include/swoc/bwf_base.h b/lib/swoc/include/swoc/bwf_base.h
index f2e69c7bb2..39e998a2fc 100644
--- a/lib/swoc/include/swoc/bwf_base.h
+++ b/lib/swoc/include/swoc/bwf_base.h
@@ -134,6 +134,9 @@ protected:
 struct Format {
   using self_type = Format; ///< Self reference type.
 
+  /// Empty format.
+  Format() = default;
+
   /// Construct from a format string @a fmt.
   Format(TextView fmt);
 
@@ -196,11 +199,16 @@ struct Format {
   /// @return @c true if all specifiers are literal.
   bool is_literal() const;
 
-protected:
-  /// Default constructor for use by subclasses with alternate formatting.
-  Format() = default;
+  using Container = std::vector<Spec>;
+  Container _items; ///< Items from format string.
 
-  std::vector<Spec> _items; ///< Items from format string.
+  using iterator = Container::iterator;
+  using const_iterator = Container::const_iterator;
+
+  iterator begin() { return _items.begin(); }
+  iterator end() { return _items.end(); }
+  const_iterator begin() const { return _items.begin(); }
+  const_iterator end() const { return _items.end(); }
 };
 
 // Name binding - support for having format specifier names.
@@ -271,11 +279,14 @@ public:
  * suitable for the subclass generators.
  */
 template <typename F> class NameMap {
-private:
-  using self_type = NameMap; ///< self reference type.
 public:
   /// Signature for generators.
   using Generator = std::function<F>;
+protected:
+  using Map = std::unordered_map<std::string_view, Generator>;
+private:
+  using self_type = NameMap; ///< self reference type.
+public:
 
   /// Construct an empty container.
   NameMap();
@@ -290,12 +301,15 @@ public:
    */
   self_type &assign(std::string_view const &name, Generator const &generator);
 
+  bool contains(std::string_view name) {
+      return _map.end() != _map.find(name);
+  }
+
 protected:
   /// Copy @a name in to local storage and return a view of it.
   std::string_view localize(std::string_view const &name);
 
   /// Name to name generator.
-  using Map = std::unordered_map<std::string_view, Generator>;
   Map _map;              ///< Defined generators.
   MemArena _arena{1024}; ///< Local name storage.
 };
diff --git a/lib/swoc/include/swoc/swoc_version.h 
b/lib/swoc/include/swoc/swoc_version.h
index 137945b003..91543d678c 100644
--- a/lib/swoc/include/swoc/swoc_version.h
+++ b/lib/swoc/include/swoc/swoc_version.h
@@ -23,11 +23,11 @@
 #pragma once
 
 #if !defined(SWOC_VERSION_NS)
-#define SWOC_VERSION_NS _1_4_10
+#define SWOC_VERSION_NS _1_5_0
 #endif
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 static constexpr unsigned MAJOR_VERSION = 1;
-static constexpr unsigned MINOR_VERSION = 4;
-static constexpr unsigned POINT_VERSION = 10;
+static constexpr unsigned MINOR_VERSION = 5;
+static constexpr unsigned POINT_VERSION = 0;
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/src/swoc_file.cc b/lib/swoc/src/swoc_file.cc
index bfb34b0298..44ea3e7426 100644
--- a/lib/swoc/src/swoc_file.cc
+++ b/lib/swoc/src/swoc_file.cc
@@ -385,6 +385,7 @@ remove_all(const path &p, std::error_code &ec)
   // Invariant - @a p is a directory.
 
   // recursively remove nested files and directories
+  //coverity[toctou : SUPPRESS]
   if (nullptr == (dir = opendir(p.c_str()))) {
     ec = std::error_code(errno, std::system_category());
     return zret;
@@ -416,6 +417,7 @@ bool remove(path const& p, std::error_code &ec) {
   } else if (::stat(p.c_str(), &fs) < 0) {
     ec = std::error_code(errno, std::system_category());
   } else if (S_ISREG(fs.st_mode)) { // regular file, try to remove it!
+    //coverity[toctou : SUPPRESS]
     if (unlink(p.c_str()) != 0) {
       ec = std::error_code(errno, std::system_category());
     }
diff --git a/lib/swoc/src/swoc_ip.cc b/lib/swoc/src/swoc_ip.cc
index 29e7f1d051..301adcaf71 100644
--- a/lib/swoc/src/swoc_ip.cc
+++ b/lib/swoc/src/swoc_ip.cc
@@ -676,11 +676,11 @@ auto
 IPMask::mask_for_quad(IP6Addr::quad_type q) -> raw_type {
   raw_type cidr = IP6Addr::QUAD_WIDTH;
   if (q != 0) {
-    auto mask = IP6Addr::QUAD_MASK;
+    IP6Addr::quad_type mask = IP6Addr::QUAD_MASK;
     do {
       mask <<= 1;
       --cidr;
-    } while ((q | ~mask) == q);
+    } while ((q & mask) == q);
     ++cidr; // loop goes exactly 1 too far.
   }
   return cidr;
@@ -842,18 +842,20 @@ auto IPSrv::assign(const sockaddr *sa) -> self_type & {
 
 bool
 IP4Net::load(TextView text) {
-  auto idx = text.find('/');
-  if (idx != text.npos) {
-    if (idx + 1 < text.size()) { // must have something past the separator or 
it's bogus.
-      IP4Addr addr;
-      if (addr.load(text.substr(0, idx))) { // load the address
-        IPMask mask;
-        text.remove_prefix(idx + 1); // drop address and separator.
-        if (mask.load(text)) {
-          this->assign(addr, mask);
-          return true;
+  if (auto mask_text = text.split_suffix_at('/') ; !mask_text.empty() ) {
+    IPMask mask;
+    bool mask_p = mask.load(mask_text);
+    if (IP4Addr addr; addr.load(text)) {
+      if (!mask_p) {
+        if (IP4Addr m ; m.load(mask_text)) {
+          mask   = IPMask::mask_for(m);
+          mask_p = (m == mask.as_ip4()); // must be an actual mask like 
address.
         }
       }
+      if (mask_p) {
+        this->assign(addr, mask);
+        return true;
+      }
     }
   }
 
@@ -863,18 +865,20 @@ IP4Net::load(TextView text) {
 
 bool
 IP6Net::load(TextView text) {
-  auto idx = text.find('/');
-  if (idx != text.npos) {
-    if (idx + 1 < text.size()) { // must have something past the separator or 
it's bogus.
-      IP6Addr addr;
-      if (addr.load(text.substr(0, idx))) { // load the address
-        IPMask mask;
-        text.remove_prefix(idx + 1); // drop address and separator.
-        if (mask.load(text)) {
-          this->assign(addr, mask);
-          return true;
+  if (auto mask_text = text.split_suffix_at('/') ; !mask_text.empty() ) {
+    IPMask mask;
+    bool mask_p = mask.load(mask_text);
+    if (IP6Addr addr; addr.load(text)) {
+      if (!mask_p) {
+        if (IP6Addr m ; m.load(mask_text)) {
+          mask   = IPMask::mask_for(m);
+          mask_p = (m == mask.as_ip6()); // must be an actual mask like 
address.
         }
       }
+      if (mask_p) {
+        this->assign(addr, mask);
+        return true;
+      }
     }
   }
 
@@ -886,11 +890,27 @@ bool
 IPNet::load(TextView text) {
   if ( auto mask_text = text.split_suffix_at('/') ; !mask_text.empty() ) {
     IPMask mask;
-    if (mask.load(mask_text)) {
-      if (IP6Addr a6; a6.load(text)) { // load the address
+    bool mask_p = mask.load(mask_text);
+
+    if (IP6Addr a6 ; a6.load(text)) { // load the address
+      if (!mask_p) {
+        if (IP6Addr m ; m.load(mask_text)) {
+          mask   = IPMask::mask_for(m);
+          mask_p = (m == mask.as_ip6()); // must be an actual mask like 
address.
+        }
+      }
+      if (mask_p) {
         this->assign(a6, mask);
         return true;
-      } else if (IP4Addr a4; a4.load(text)) {
+      }
+    } else if (IP4Addr a4 ; a4.load(text)) {
+      if (!mask_p) {
+        if (IP4Addr m ; m.load(mask_text)) {
+          mask   = IPMask::mask_for(m);
+          mask_p = (m == mask.as_ip4()); // must be an actual mask like 
address.
+        }
+      }
+      if (mask_p) {
         this->assign(a4, mask);
         return true;
       }
@@ -1212,4 +1232,30 @@ IP6Range::NetSource::search_narrower() {
   }
 }
 
+bool
+IPRangeView::operator==(IPRange const &r) const {
+  if (_family == r.family()) {
+    if (AF_INET == _family) {
+      return *_raw._4 == r.ip4();
+    } else if (AF_INET6 == _family) {
+      return *_raw._6 == r.ip6();
+    }
+    return true;
+  }
+  return false;
+}
+
+bool
+IPRangeView::operator==(IPRangeView::self_type const &that) const {
+  if (_family == that._family) { // different families are not equal
+    if (AF_INET == _family) {
+      return (_raw._4 == that._raw._4) || (*_raw._4 == *that._raw._4);
+    } else if (AF_INET6 == _family) {
+      return (_raw._6 == that._raw._6) || (*_raw._6 == *that._raw._6);
+    }
+    return true;
+  }
+  return false;
+}
+
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/proxy/ControlMatcher.cc b/proxy/ControlMatcher.cc
index 6e072105d0..a633679e97 100644
--- a/proxy/ControlMatcher.cc
+++ b/proxy/ControlMatcher.cc
@@ -651,7 +651,7 @@ template <class Data, class MatchResult>
 void
 IpMatcher<Data, MatchResult>::Match(sockaddr const *addr, RequestData *rdata, 
MatchResult *result) const
 {
-  if (auto &&[range, data]{*ip_addrs.find(swoc::IPAddr(addr))}; 
!range.empty()) {
+  if (auto [range, data]{*ip_addrs.find(swoc::IPAddr(addr))}; !range.empty()) {
     ink_assert(data != nullptr);
     data->UpdateMatch(result, rdata);
   }

Reply via email to