IPSpace fixes.
diff --git a/swoc++/include/swoc/DiscreteRange.h b/swoc++/include/swoc/DiscreteRange.h
index 6838b14..f98f7db 100644
--- a/swoc++/include/swoc/DiscreteRange.h
+++ b/swoc++/include/swoc/DiscreteRange.h
@@ -1,4 +1,5 @@
#pragma once
+
// SPDX-License-Identifier: Apache-2.0
// Copyright 2014 Network Geographics
@@ -64,7 +65,7 @@
} // namespace detail
/// Relationship between two intervals.
-enum DiscreteRangeRelation {
+enum class DiscreteRangeRelation : uint8_t {
NONE, ///< No common elements.
EQUAL, ///< Identical ranges.
SUBSET, ///< All elements in LHS are also in RHS.
@@ -73,6 +74,14 @@
ADJACENT ///< The two intervals are adjacent and disjoint.
};
+/// Relationship between one edge of an interval and the "opposite" edge of another.
+enum class DiscreteRangeEdgeRelation : uint8_t {
+ NONE, ///< Edge is on the opposite side of the relating edge.
+ GAP, ///< There is a gap between the edges.
+ ADJ, ///< The edges are adjacent.
+ OVLP, ///< Edge is inside interval.
+};
+
/** A range over a discrete finite value metric.
@tparam T The type for the range values.
@@ -100,6 +109,7 @@
public:
using metric_type = T;
using Relation = DiscreteRangeRelation;
+ using EdgeRelation = DiscreteRangeEdgeRelation;
// static constexpr self_type ALL{detail::minimum<metric_type>(), detail::maximum<metric_type>()};
@@ -113,7 +123,7 @@
*
* @note Not marked @c explicit and so serves as a conversion from scalar values to an interval.
*/
- constexpr DiscreteRange(T const &value) : _min(value), _max(value){};
+ constexpr DiscreteRange(T const &value) : _min(value), _max(value) {};
/** Constructor.
*
@@ -173,6 +183,13 @@
*/
bool is_adjacent_to(self_type const &that) const;
+ /** Test for @a this being adjacent on the left of @a that.
+ *
+ * @param that Range to check for adjacency.
+ * @return @c true if @a this ends exactly the value before @a that begins.
+ */
+ bool is_left_adjacent_to(self_type const& that) const;
+
//! Test if the union of two intervals is also an interval.
bool has_union(self_type const &that) const;
@@ -201,6 +218,27 @@
*/
Relation relationship(self_type const &that) const;
+ /** Determine the relationship of the left edge of @a that with @a this.
+ *
+ * @param that The other interval.
+ * @return The edge relationship.
+ *
+ * This checks the right edge of @a this against the left edge of @a that.
+ *
+ * - GAP: @a that left edge is right of @a this.
+ * - ADJ: @a that left edge is right adjacent to @a this.
+ * - 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) {
+ auto tmp{_max};
+ ++tmp;
+ return tmp < that._max ? EdgeRelation::GAP : EdgeRelation::ADJ;
+ }
+ return _min >= that._min ? EdgeRelation::NONE : EdgeRelation::OVLP;
+ }
+
/** 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.
@@ -287,18 +325,18 @@
template <typename T>
typename DiscreteRange<T>::Relation
DiscreteRange<T>::relationship(self_type const &that) const {
- Relation retval = NONE;
+ Relation retval = Relation::NONE;
if (this->has_intersection(that)) {
if (*this == that)
- retval = EQUAL;
+ retval = Relation::EQUAL;
else if (this->is_subset_of(that))
- retval = SUBSET;
+ retval = Relation::SUBSET;
else if (this->is_superset_of(that))
- retval = SUPERSET;
+ retval = Relation::SUPERSET;
else
- retval = OVERLAP;
+ retval = Relation::OVERLAP;
} else if (this->is_adjacent_to(that)) {
- retval = ADJACENT;
+ retval = Relation::ADJACENT;
}
return retval;
}
@@ -417,6 +455,12 @@
template <typename T>
bool
DiscreteRange<T>::is_adjacent_to(DiscreteRange::self_type const &that) const {
+ return this->is_left_adjacent_to(that) || that.is_left_adjacent_to(*this);
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_left_adjacent_to(DiscreteRange::self_type const &that) const {
/* Need to be careful here. We don't know much about T and we certainly don't know if "t+1"
* even compiles for T. We do require the increment operator, however, so we can use that on a
* copy to get the equivalent of t+1 for adjacency testing. We must also handle the possibility
@@ -427,9 +471,6 @@
if (_max < that._min) {
T x(_max);
return ++x == that._min;
- } else if (that._max < _min) {
- T x(that._max);
- return ++x == _min;
}
return false;
}
@@ -578,6 +619,8 @@
*/
self_type & assign(PAYLOAD const &payload);
+ range_type const& range() const { return _range; }
+
self_type &
assign_min(METRIC const &m) {
_range.assign_min(m);
@@ -1167,16 +1210,15 @@
// Used to hold a temporary blended node - @c release if put in space, otherwise cleaned up.
using unique_node = std::unique_ptr<Node, decltype(node_cleaner)>;
- // Rightmost node of interest with n->_min <= min.
+ // Rightmost node of interest with n->min() <= range.min().
Node *n = this->lower_bound(range.min());
- Node *pred = nullptr;
// This doesn't change, compute outside loop.
auto range_max_plus_1 = range.max();
++range_max_plus_1; // only use in contexts where @a max < METRIC max value.
- // Update every loop to be the place to start blending.
- auto range_min = range.min();
+ // Update every loop to track what remains to be filled.
+ auto remaining = range;
if (nullptr == n) {
n = this->head();
@@ -1184,61 +1226,75 @@
// Process @a n, covering the values from the previous range to @a n.max
while (n) {
- // min is the smallest value of interest, need to fill from there on to the right.
- metric_type min, min_minus_1;
-
- pred = n ? prev(n) : nullptr;
- if (pred && pred->max() >= range.min()) {
- min_minus_1 = min = pred->max();
- ++min;
- } else {
- min_minus_1 = min = range.min();
- --min_minus_1;
- if (pred && pred->max() < min_minus_1) {
- pred = nullptr;
- }
+ // Always look back at prev, so if there's no overlap at all, skip it.
+ if (n->max() < remaining.min()) {
+ n = next(n);
+ continue;
}
- // if @a pred is set, then it's adjacent or overlapping on the left.
- // Do some important computations once, caching the results.
- // @a n extends past @a range, so the trailing segment must be dealt with.
- bool right_ext_p = n->max() > range.max();
- // @a n overlaps with @a range, but only to the right.
- bool right_overlap_p = n->min() <= range.max() && n->min() >= range.min();
- // @a n is adjacent on the right to @a range.
- bool right_adj_p = !right_overlap_p && n->min() == range_max_plus_1;
+ // Invariant - n->max() >= remaining.min();
+ Node* pred = prev(n);
+
+ // Check for left extension. If found, clip that node to be adjacent and put in a
+ // temporary that covers the overlap with the original payload.
+ if (n->min() < remaining.min()) {
+ auto stub = _fa.make(remaining.min(), n->max(), n->payload());
+ auto x { remaining.min() };
+ --x;
+ n->assign_max(x);
+ this->insert_after(n, stub);
+ pred = n;
+ n = stub;
+ }
+
+ auto pred_edge = pred ? remaining.left_edge_relationship(pred->range()) : DiscreteRangeEdgeRelation::NONE;
+ // invariant - pred->max() < remaining.min()
+
+ // Calculate and cache key relationships between @a n and @a remaining.
+
+ // @a n extends past @a remaining, so the trailing segment must be dealt with.
+ bool right_ext_p = n->max() > remaining.max();
+ // @a n strictly right overlaps with @a remaining.
+ bool right_overlap_p = remaining.contains(n->min());
+ // @a n is adjacent on the right to @a remaining.
+ bool right_adj_p = remaining.is_left_adjacent_to(n->range());
// @a n has the same color as would be used for unmapped values.
bool n_plain_colored_p = plain_color_p && (n->payload() == plain_color);
- // @a pred has the same color as would be used for unmapped values.
- bool pred_plain_colored_p = plain_color_p && pred && pred->payload() == plain_color;
+ // @a rped has the same color as would be used for unmapped values.
+ bool pred_plain_colored_p =
+ (DiscreteRangeEdgeRelation::NONE != pred_edge && DiscreteRangeEdgeRelation::GAP != pred_edge) &&
+ pred->payload() == plain_color;
- // Check for no right overlap - that means the next node is past the target range.
+ // Check for no right overlap - that means @a n is past the target range.
+ // It may be possible to extend @a n or the previous range to cover
+ // the target range. Regardless, all of @a range can be filled at this point.
if (!right_overlap_p) {
if (right_adj_p && n_plain_colored_p) { // can pull @a n left to cover
- n->assign_min(min);
+ n->assign_min(remaining.min());
if (pred_plain_colored_p) { // if that touches @a pred with same color, collapse.
n->assign_min(pred->min());
this->remove(pred);
}
} else if (pred_plain_colored_p) { // can pull @a pred right to cover.
- pred->assign_max(range.max());
- } else { // Must add new range.
- this->insert_after(n, _fa.make(min, range.max(), plain_color));
+ pred->assign_max(remaining.max());
+ } else if (! remaining.is_empty()) { // Must add new range.
+ this->insert_before(n, _fa.make(remaining.min(), remaining.max(), plain_color));
}
return *this;
}
- // Invariant: There is overlap between @a n and @a range.
+ // Invariant: @n has right overlap with @a remaining
- // Fill from @a min to @a n.min - 1
- if (plain_color_p && min < n->min()) { // can fill and there's space to fill.
+ // If there's a gap on the left, fill from @a min to @a n.min - 1
+ // Also see above - @a pred is set iff it is left overlapping or left adjacent.
+ if (plain_color_p && remaining.min() < n->min()) {
if (n->payload() == plain_color) {
if (pred && pred->payload() == n->payload()) {
auto pred_min{pred->min()};
this->remove(pred);
n->assign_min(pred_min);
} else {
- n->assign_min(min);
+ n->assign_min(remaining.min());
}
} else {
auto n_min_minus_1{n->min()};
@@ -1246,18 +1302,19 @@
if (pred && pred->payload() == plain_color) {
pred->assign_max(n_min_minus_1);
} else {
- this->insert_before(n, _fa.make(min, n_min_minus_1, plain_color));
+ this->insert_before(n, _fa.make(range.min(), n_min_minus_1, plain_color));
}
}
}
+ // Invariant: Space in @a range and to the left of @a n has been filled.
+
// Create a node with the blend for the overlap and then update / replace @a n as needed.
- auto max { right_ext_p ? range.max() : n->max() }; // smallest boundary of range and @a n.
+ auto max { right_ext_p ? remaining.max() : n->max() }; // smallest boundary of range and @a n.
unique_node fill { _fa.make(n->min(), max, n->payload()), node_cleaner };
bool fill_p = blender(fill->payload(), color); // fill or clear?
auto next_n = next(n); // cache this in case @a n is removed.
- range_min = fill->max(); // blend will be updated to one past @a fill
- ++range_min;
+ remaining.assign_min(++METRIC{fill->max()}); // Update what is left to fill.
// Clean up the range for @a n
if (fill_p) {
@@ -1270,10 +1327,8 @@
return *this;
}
} else {
- // PROBLEM - not collapsing into @a pred(n) when it's adjacent / matching color.
- // But @a pred may have been removed above, not reliable at this point.
- pred = prev(n);
- if (pred && pred->payload() == fill->payload()) {
+ // Collapse in to previous range if it's adjacent and the color matches.
+ if (nullptr != (pred = prev(n)) && pred->range().is_left_adjacent_to(fill->range()) && pred->payload() == fill->payload()) {
this->remove(n);
pred->assign_max(fill->max());
} else {
@@ -1281,13 +1336,11 @@
this->remove(n);
}
}
+ } else if (right_ext_p) {
+ n->assign_min(range_max_plus_1);
+ return *this;
} else {
- if (right_ext_p) {
- n->assign_min(range_max_plus_1);
- return *this;
- } else {
- this->remove(n);
- }
+ this->remove(n);
}
// Everything up to @a n.max is correct, time to process next node.
@@ -1296,14 +1349,14 @@
// Arriving here means there are no more ranges past @a range (those cases return from the loop).
// Therefore the final fill node is always last in the tree.
- if (plain_color_p && range_min <= range.max()) {
+ if (plain_color_p && remaining.min() <= range.max()) {
// Check if the last node can be extended to cover because it's left adjacent.
// Can decrement @a range_min because if there's a range to the left, @a range_min is not minimal.
n = _list.tail();
- if (n && n->max() >= --range_min && n->payload() == plain_color) {
+ if (n && n->max() >= --METRIC{remaining.min()} && n->payload() == plain_color) {
n->assign_max(range.max());
} else {
- this->append(_fa.make(range_min, range.max(), plain_color));
+ this->append(_fa.make(remaining.min(), remaining.max(), plain_color));
}
}
diff --git a/swoc++/include/swoc/IntrusiveDList.h b/swoc++/include/swoc/IntrusiveDList.h
index 4d69aa9..f91079c 100644
--- a/swoc++/include/swoc/IntrusiveDList.h
+++ b/swoc++/include/swoc/IntrusiveDList.h
@@ -148,10 +148,31 @@
/// Inequality
bool operator!=(self_type const &that) const;
+ /// Check if @c std::prev would be valid.
+ /// @return @c true if calling @c std::prev would yield a valid iterator.
+ /// @note Identical to @c has_predecessor.
+ bool has_prev() const;
+
+ /// Check if @c std::next would be valid.
+ /// @return @c true if calling @c std::next would yield a valid iterator.
+ bool has_next() const;
+
+ /// Check if there is a predecessor.
+ /// @return @c true if there is a predecessor element for the current element.
+ /// @note Identical to @c has_prev.
+ bool has_predecessor() const;
+
+ /// Check if there is a successor.
+ /// @return @c true if after incrementing, the iterator will reference a value.
+ /// @note This is subtly different from @c has_net. It is false for the last element in the
+ /// iteration. Even though incrementing such an iterator would valid, there would not be a
+ /// successor element.
+ bool has_successor() const;
+
protected:
// These are stored non-const to make implementing @c iterator easier. This class provides the required @c const
// protection.
- list_type *_list{nullptr}; ///< Needed to descrement from @c end() position.
+ list_type *_list{nullptr}; ///< Needed to decrement from @c end() position.
typename list_type::value_type *_v{nullptr}; ///< Referenced element.
/// Internal constructor for containers.
@@ -579,6 +600,26 @@
return this->_v != that._v;
}
+template<typename L>
+bool IntrusiveDList<L>::const_iterator::has_predecessor() const {
+ return _v ? nullptr != L::prev_ptr(_v) : ! _list->empty();
+}
+
+template<typename L>
+bool IntrusiveDList<L>::const_iterator::has_prev() const {
+ return _v ? nullptr != L::prev_ptr(_v) : ! _list->empty();
+}
+
+template<typename L>
+bool IntrusiveDList<L>::const_iterator::has_successor() const {
+ return _v && nullptr != L::next_ptr(_v);
+}
+
+template<typename L>
+bool IntrusiveDList<L>::const_iterator::has_next() const {
+ return nullptr != _v;
+}
+
template <typename L>
auto
IntrusiveDList<L>::prepend(value_type *v) -> self_type & {
diff --git a/swoc++/include/swoc/bwf_ip.h b/swoc++/include/swoc/bwf_ip.h
index 2b19d7f..63906d8 100644
--- a/swoc++/include/swoc/bwf_ip.h
+++ b/swoc++/include/swoc/bwf_ip.h
@@ -20,6 +20,7 @@
#pragma once
+#include <iosfwd>
#include "swoc/bwf_base.h"
#include "swoc/swoc_ip.h"
#include <netinet/in.h>
@@ -29,13 +30,60 @@
// All of these expect the address to be in network order.
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr);
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, in6_addr const &addr);
-BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IP4Addr const &addr);
-BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IPAddr const &addr);
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr);
+// Use class information for ordering.
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IP4Addr const &addr);
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IP6Addr const &addr);
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IPAddr const &addr);
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IP4Range const &Range);
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IP6Range const &Range);
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IPRange const &Range);
inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, IPEndpoint const &addr) {
return bwformat(w, spec, &addr.sa);
}
+/// Buffer space sufficient for printing any basic IP address type.
+static const size_t IP_STREAM_SIZE = 80;
+
} // namespace swoc
+
+namespace std {
+inline ostream &
+operator<<(ostream &s, swoc::IP4Addr const &addr) {
+ swoc::LocalBufferWriter<swoc::IP_STREAM_SIZE> w;
+ return s << bwformat(w, swoc::bwf::Spec::DEFAULT, addr);
+}
+
+inline ostream &
+operator<<(ostream &s, swoc::IP6Addr const &addr) {
+ swoc::LocalBufferWriter<swoc::IP_STREAM_SIZE> w;
+ return s << bwformat(w, swoc::bwf::Spec::DEFAULT, addr);
+}
+
+inline ostream &
+operator<<(ostream &s, swoc::IPAddr const &addr) {
+ swoc::LocalBufferWriter<swoc::IP_STREAM_SIZE> w;
+ return s << bwformat(w, swoc::bwf::Spec::DEFAULT, addr);
+}
+
+inline ostream &
+operator<<(ostream &s, swoc::IP4Range const &Range) {
+ swoc::LocalBufferWriter<swoc::IP_STREAM_SIZE> w;
+ return s << bwformat(w, swoc::bwf::Spec::DEFAULT, Range);
+}
+
+inline ostream &
+operator<<(ostream &s, swoc::IP6Range const &Range) {
+ swoc::LocalBufferWriter<swoc::IP_STREAM_SIZE> w;
+ return s << bwformat(w, swoc::bwf::Spec::DEFAULT, Range);
+}
+
+inline ostream &
+operator<<(ostream &s, swoc::IPRange const &Range) {
+ swoc::LocalBufferWriter<swoc::IP_STREAM_SIZE> w;
+ return s << bwformat(w, swoc::bwf::Spec::DEFAULT, Range);
+}
+} // namespace std
+
diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h
index 66213cb..ebd365a 100644
--- a/swoc++/include/swoc/swoc_ip.h
+++ b/swoc++/include/swoc/swoc_ip.h
@@ -8,9 +8,9 @@
#include <string_view>
#include <variant>
+#include <swoc/TextView.h>
#include <swoc/DiscreteRange.h>
#include <swoc/RBTree.h>
-#include "bwf_base.h"
namespace swoc
{
@@ -121,7 +121,7 @@
/// @return This object.
self_type &set_to_any(int family);
- /// Set to be loopback for family @a family.
+ /// Set to be loopback address for family @a family.
/// @a family must be @c AF_INET or @c AF_INET6.
/// @return This object.
self_type &set_to_loopback(int family);
@@ -161,30 +161,49 @@
constexpr IP4Addr() = default; ///< Default constructor - invalid result.
- /// Construct using IPv4 @a addr.
+ /// Construct using IPv4 @a addr (in host order).
+ /// @note Host order seems odd, but all of the standard network macro values such as @c INADDR_LOOPBACK
+ /// are in host order.
explicit constexpr IP4Addr(in_addr_t addr);
/// Construct from @c sockaddr_in.
- explicit IP4Addr(sockaddr_in const *addr);
+ explicit IP4Addr(sockaddr_in const *sa);
/// Construct from text representation.
/// If the @a text is invalid the result is an invalid instance.
IP4Addr(string_view const &text);
+ /// Construct from generic address @a addr.
+ explicit IP4Addr(IPAddr const& addr);
/// Assign from IPv4 raw address.
self_type &operator=(in_addr_t ip);
/// Set to the address in @a addr.
- self_type &operator=(sockaddr_in const *addr);
+ self_type &operator=(sockaddr_in const *sa);
+ /// Increment address.
self_type &operator++();
+ /// Decrement address.
self_type &operator--();
+ /** Byte access.
+ *
+ * @param idx Byte index.
+ * @return The byte at @a idx in the address.
+ */
+ uint8_t operator [] (unsigned idx) const {
+ return reinterpret_cast<bytes const&>(_addr)[idx];
+ }
+
+ /// Apply @a mask to address, leaving the network portion.
self_type &operator&=(IPMask const& mask);
+ /// Apply @a mask to address, creating the broadcast address.
self_type &operator|=(IPMask const& mask);
- /// Write to @c sockaddr.
- sockaddr *fill(sockaddr_in *sa, in_port_t port = 0) const;
+ /// Write this adddress and @a port to the sockaddr @a sa.
+ sockaddr_in *fill(sockaddr_in *sa, in_port_t port = 0) const;
+ /// @return The address in network order.
in_addr_t network_order() const;
+ /// @return The address in host order.
in_addr_t host_order() const;
/** Parse @a text as IPv4 address.
@@ -200,22 +219,28 @@
/// Get the IP address family.
/// @return @c AF_INET
- sa_family_t family() const;
-
- /// Conversion to base type.
- operator in_addr_t() const;
+ sa_family_t family() const { return AF_INET; }
/// Test for multicast
- bool is_multicast() const;
+ bool is_multicast() const { return IN_MULTICAST(_addr); }
/// Test for loopback
- bool is_loopback() const;
+ bool is_loopback() const { return (*this)[0] == IN_LOOPBACKNET; }
- constexpr static in_addr_t reorder(in_addr_t src) {
- return ((src & 0xFF) << 24) | (((src >> 8) & 0xFF) << 16) | (((src >> 16) & 0xFF) << 8) | ((src >> 24) & 0xFF);
- }
+ /** Convert between network and host order.
+ *
+ * @param src Input address.
+ * @return @a src with the byte reversed.
+ *
+ * This performs the same computation as @c ntohl and @c htonl but is @c constexpr to be usable
+ * in situations those two functions are not.
+ */
+ constexpr static in_addr_t reorder(in_addr_t src);
protected:
+ /// Access by bytes.
+ using bytes = std::array<uint8_t, 4>;
+
friend bool operator==(self_type const &, self_type const &);
friend bool operator!=(self_type const &, self_type const &);
friend bool operator<(self_type const &, self_type const &);
@@ -236,12 +261,16 @@
static constexpr size_t WORD_SIZE = sizeof(uint64_t); ///< Size of words used to store address.
static constexpr size_t N_QUADS = SIZE / sizeof(QUAD); ///< # of quads in an IPv6 address.
- /// Type for the actual address.
+ /// Direct access type for the address.
/// Equivalent to the data type for data member @c s6_addr in @c in6_addr.
using raw_type = std::array<unsigned char, SIZE>;
+ /// Direct access type for the address by quads (16 bits).
+ /// This corresponds to the elements of the text format of the address.
using quad_type = std::array<unsigned short, N_QUADS>;
+ /// Minimum value of an address.
static const self_type MIN;
+ /// Maximum value of an address.
static const self_type MAX;
IP6Addr() = default; ///< Default constructor - 0 address.
@@ -256,8 +285,13 @@
/// If the @a text is invalid the result is an invalid instance.
IP6Addr(string_view const& text);
+ /// Construct from generic @a addr.
+ IP6Addr(IPAddr const& addr);
+
+ /// Increment address.
self_type &operator++();
+ /// Decrement address.
self_type &operator--();
/// Assign from IPv6 raw address.
@@ -271,6 +305,7 @@
/// Copy address to @a addr in network order.
in6_addr & copy_to(in6_addr & addr) const;
+ /// Return the address in network order.
in6_addr network_order() const;
/** Parse a string for an IP address.
@@ -287,13 +322,13 @@
/// Get the address family.
/// @return The address family.
- sa_family_t family() const;
+ sa_family_t family() const { return AF_INET6; }
/// Test for multicast
- bool is_multicast() const;
+ bool is_loopback() const { return IN6_IS_ADDR_LOOPBACK(_addr._raw.data()); }
/// Test for loopback
- bool is_loopback() const;
+ bool is_multicast() const { return IN6_IS_ADDR_MULTICAST(_addr._raw.data()); }
self_type & clear() {
_addr._u64[0] = _addr._u64[1] = 0;
@@ -309,14 +344,23 @@
friend bool operator!=(self_type const &, self_type const &);
friend bool operator<(self_type const &, self_type const &);
friend bool operator<=(self_type const &, self_type const &);
+
+ /// Type for digging around inside the address, with the various forms of access.
union {
uint64_t _u64[2] = {0, 0}; ///< 0 is MSW, 1 is LSW.
quad_type _quad; ///< By quad.
raw_type _raw; ///< By byte.
} _addr;
+ /// Index of quads in @a _addr._quad.
+ /// This converts from the position in the text format to the quads in the binary format.
static constexpr std::array<unsigned, N_QUADS> QUAD_IDX = { 3,2,1,0,7,6,5,4 };
+ /** Construct from two 64 bit values.
+ *
+ * @param msw The most significant 64 bits, host order.
+ * @param lsw The least significant 64 bits, host order.
+ */
IP6Addr(uint64_t msw, uint64_t lsw) : _addr{msw, lsw} {}
};
@@ -343,7 +387,7 @@
explicit IPAddr(IPEndpoint const &addr);
/// Construct from text representation.
/// If the @a text is invalid the result is an invalid instance.
- explicit IPAddr(string_view text);
+ explicit IPAddr(string_view const& text);
/// Set to the address in @a addr.
self_type &assign(sockaddr const *addr);
@@ -396,6 +440,10 @@
in_addr_t network_ip4() const;
in6_addr network_ip6() const;
+ explicit operator IP4Addr const&() const { return _addr._ip4; }
+
+ explicit operator IP6Addr const&() const { return _addr._ip6; }
+
/// Test for validity.
bool is_valid() const;
@@ -413,6 +461,8 @@
protected:
friend bool operator==(self_type const &, self_type const &);
+ friend IP4Addr;
+ friend IP6Addr;
/// Address data.
union raw_addr_type {
@@ -457,7 +507,7 @@
raw_type width() const;
/// Family type.
- sa_family_t family() const;
+ sa_family_t family() const { return _family; }
/// Write the mask as an address to @a addr.
/// @return The filled address.
@@ -479,9 +529,16 @@
public:
using super_type::super_type; ///< Import super class constructors.
+ /// Default constructor, invalid range.
IP4Range() = default;
+
+ /// Construct from an network expressed as @a addr and @a mask.
IP4Range(IP4Addr const& addr, IPMask const& mask);
+ /// Construct from super type.
+ /// @internal Why do I have to do this, even though the super type constructors are inherited?
+ IP4Range(super_type const& r) : super_type(r) {}
+
/** Construct range from @a text.
*
* @param text Range text.
@@ -490,18 +547,24 @@
* This results in a zero address if @a text is not a valid string. If this should be checked,
* use @c load.
*/
- IP4Range(string_view text) {
- this->load(text);
- }
+ IP4Range(string_view const& text);
+ /** Set @a this range.
+ *
+ * @param addr Minimum address.
+ * @param mask CIDR mask to compute maximum adddress from @a addr.
+ * @return @a this
+ */
self_type & assign(IP4Addr const& addr, IPMask const& mask);
/** Assign to this range from text.
+ *
+ * @param text Range text.
+ *
* The text must be in one of three formats.
* - A dashed range, "addr1-addr2"
* - A singleton, "addr". This is treated as if it were "addr-addr", a range of size 1.
* - CIDR notation, "addr/cidr" where "cidr" is a number from 0 to the number of bits in the address.
- * @param text Range text.
*/
bool load(string_view text);
@@ -515,8 +578,27 @@
public:
using super_type::super_type; ///< Import super class constructors.
+ /// Construct from super type.
+ /// @internal Why do I have to do this, even though the super type constructors are inherited?
+ IP6Range(super_type const& r) : super_type(r) {}
+
+ /** Set @a this range.
+ *
+ * @param addr Minimum address.
+ * @param mask CIDR mask to compute maximum adddress from @a addr.
+ * @return @a this
+ */
self_type & assign(IP6Addr const& addr, IPMask const& mask);
+ /** Assign to this range from text.
+ *
+ * @param text Range text.
+ *
+ * The text must be in one of three formats.
+ * - A dashed range, "addr1-addr2"
+ * - A singleton, "addr". This is treated as if it were "addr-addr", a range of size 1.
+ * - CIDR notation, "addr/cidr" where "cidr" is a number from 0 to the number of bits in the address.
+ */
bool load(string_view text);
};
@@ -524,11 +606,26 @@
class IPRange {
using self_type = IPRange;
public:
+ /// Default constructor - construct invalid range.
IPRange() = default;
+ /// Construct from an IPv4 @a range.
+ IPRange(IP4Range const& range);
+ /// Construct from an IPv6 @a range.
+ IPRange(IP6Range const& range);
+ /** Construct from a string format.
+ *
+ * @param text Text form of range.
+ *
+ * The string can be a single address, two addresses separated by a dash '-' or a CIDR network.
+ */
+ IPRange(string_view const& text);
- bool is(sa_family_t f) const {
- return f == _family;
- }
+ /** Check if @a this range is the IP address @a family.
+ *
+ * @param family IP address family.
+ * @return @c true if this is @a family, @c false if not.
+ */
+ bool is(sa_family_t family) const ;
/** Load the range from @a text.
*
@@ -540,7 +637,9 @@
*/
bool load(std::string_view const& text);
+ /// @return The minimum address in the range.
IPAddr min() const;
+ /// @return The maximum address in the range.
IPAddr max() const;
operator IP4Range & () { return _range._ip4; }
@@ -644,7 +743,7 @@
*
* All addresses in @a r are set to have the @a payload.
*/
- self_type & mark(IP4Range const &r, PAYLOAD const &payload);
+ self_type & mark(IPRange const &range, PAYLOAD const &payload);
/** Fill the @a range with @a payload.
*
@@ -704,13 +803,256 @@
/// Remove all ranges.
void clear();
- typename IP4Space::iterator begin() { return _ip4.begin(); }
- typename IP4Space::iterator end() { return _ip4.end(); }
+ /** Constant iterator.
+ * THe value type is a tuple of the IP address range and the @a PAYLOAD. Both are constant.
+ *
+ * @internal THe non-const iterator is a subclass of this, in order to share implementation. This
+ * also makes it easy to convert from iterator to const iterator, which is desirable.
+ */
+ class const_iterator {
+ using self_type = const_iterator; ///< Self reference type.
+ friend class IPSpace;
+ public:
+ using value_type = std::tuple<IPRange const, PAYLOAD const&>; /// Import for API compliance.
+ // STL algorithm compliance.
+ using iterator_category = std::bidirectional_iterator_tag;
+ using pointer = value_type *;
+ using reference = value_type &;
+ using difference_type = int;
+
+ /// Default constructor.
+ const_iterator() = default;
+
+ /// Pre-increment.
+ /// Move to the next element in the list.
+ /// @return The iterator.
+ self_type &operator++();
+
+ /// Pre-decrement.
+ /// Move to the previous element in the list.
+ /// @return The iterator.
+ self_type &operator--();
+
+ /// Post-increment.
+ /// Move to the next element in the list.
+ /// @return The iterator value before the increment.
+ 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);
+
+ /// Dereference.
+ /// @return A reference to the referent.
+ value_type const& operator*() const;
+
+ /// Dereference.
+ /// @return A pointer to the referent.
+ value_type const* operator->() const;
+
+ /// Equality
+ bool operator==(self_type const &that) const;
+
+ /// Inequality
+ bool operator!=(self_type const &that) const;
+
+ protected:
+ // These are stored non-const to make implementing @c iterator easier. This class provides the
+ // required @c const protection. This is basic a tuple of iterators - for forward iteration if
+ // the primary (ipv4) iterator is at the end, then use the secondary (ipv6) iterator. The reverse
+ // is done for reverse iteration. This depends on the extra support @c IntrusiveDList iterators
+ // provide.
+ typename IP4Space::iterator _iter_4; ///< IPv4 sub-space iterator.
+ typename IP6Space::iterator _iter_6; ///< IPv6 sub-space iterator.
+ /// Current value.
+ value_type _value { IPRange{}, *null_payload };
+
+ /// Dummy payload.
+ /// @internal Used to initialize @c value_type for invalid iterators.
+ static constexpr PAYLOAD * null_payload = nullptr;
+
+ /** Internal constructor.
+ *
+ * @param iter4 Starting place for IPv4 subspace.
+ * @param iter6 Starting place for IPv6 subspace.
+ *
+ * In practice, both iterators should be either the beginning or ending iterator for the subspace.
+ */
+ const_iterator(typename IP4Space::iterator const& iter4, typename IP6Space::iterator const& iter6);
+ };
+
+ /** Iterator.
+ * THe value type is a tuple of the IP address range and the @a PAYLOAD. The range is constant
+ * and the @a PAYLOAD is a reference. This can be used to update the @a PAYLOAD for this range.
+ *
+ * @note Range merges are not trigged by modifications of the @a PAYLOAD via an iterator.
+ */
+ class iterator : public const_iterator {
+ using self_type = iterator;
+ using super_type = const_iterator;
+ friend class IPSpace;
+ public:
+ public:
+ /// Value type of iteration.
+ using value_type = std::tuple<IPRange const, PAYLOAD&>;
+ using pointer = value_type *;
+ using reference = value_type &;
+
+ /// Default constructor.
+ iterator() = default;
+
+ /// Pre-increment.
+ /// Move to the next element in the list.
+ /// @return The iterator.
+ self_type &operator++();
+
+ /// Pre-decrement.
+ /// Move to the previous element in the list.
+ /// @return The iterator.
+ self_type &operator--();
+
+ /// 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; }
+
+ /// 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; }
+
+ /// Dereference.
+ /// @return A reference to the referent.
+ value_type const& operator*() const;
+
+ /// Dereference.
+ /// @return A pointer to the referent.
+ value_type const* operator->() const;
+
+ protected:
+ using super_type::super_type;
+ };
+
+ const_iterator begin() const { return const_iterator(_ip4.begin(), _ip6.begin()); }
+ const_iterator end() const { return const_iterator(_ip4.end(), _ip6.end()); }
+
+ iterator begin() { return iterator{_ip4.begin(), _ip6.begin()}; }
+ iterator end() { return iterator{_ip4.end(), _ip6.end()}; }
protected:
- IP4Space _ip4;
- IP6Space _ip6;
+ IP4Space _ip4; ///< Sub-space containing IPv4 ranges.
+ IP6Space _ip6; ///< sub-space containing IPv6 ranges.
};
+
+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()};
+ } else if (_iter_6.has_next()) {
+ new(&_value) value_type{_iter_6->range(), _iter_6->payload()};
+ }
+}
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::const_iterator::operator++() -> self_type & {
+ bool incr_p = false;
+ if (_iter_4.has_next()) {
+ ++_iter_4;
+ incr_p = true;
+ if (_iter_4.has_next()) {
+ new(&_value) value_type{_iter_4->range(), _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()};
+ return *this;
+ }
+ }
+ new (&_value) value_type{IPRange{}, *null_payload};
+ return *this;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::const_iterator::operator++(int) -> self_type {
+ self_type zret(*this);
+ ++*this;
+ return zret;
+}
+
+template<typename PAYLOAD>
+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()};
+ return *this;
+ }
+ if (_iter_4.has_prev()) {
+ --_iter_4;
+ new(&_value) value_type{_iter_4->range(), _iter_4->payload()};
+ return *this;
+ }
+ new (&_value) value_type{IPRange{}, *null_payload};
+ return *this;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::const_iterator::operator--(int) -> self_type {
+ self_type zret(*this);
+ --*this;
+ return zret;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::const_iterator::operator*() const -> value_type const& { return _value; }
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::const_iterator::operator->() const -> value_type const * { return &_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
+ * neccesary to do the @a _iter_4 validity on both iterators to avoid the case of a false positive
+ * where different internal iterators are valid. However, in practice the other (non-active)
+ * iterator won't have an arbitrary value, it will be either @c begin or @c end in step with the
+ * active iterator therefore it's effective and cheaper to just check both values.
+ */
+
+template<typename PAYLOAD>
+bool
+IPSpace<PAYLOAD>::const_iterator::operator==(self_type const& that) const {
+ return _iter_4 == that._iter_4 && _iter_6 == that._iter_6;
+}
+
+template<typename PAYLOAD>
+bool
+IPSpace<PAYLOAD>::const_iterator::operator!=(self_type const& that) const {
+ return _iter_4 != that._iter_4 || _iter_6 != that._iter_6;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::iterator::operator->() const -> value_type const* {
+ return static_cast<value_type*>(&super_type::_value);
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::iterator::operator*() const -> value_type const& {
+ return reinterpret_cast<value_type const&>(super_type::_value);
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::iterator::operator++() -> self_type & {
+ this->super_type::operator++();
+ return *this;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::iterator::operator--() -> self_type & {
+ this->super_type::operator--();
+ return *this;
+}
+
// --------------------------------------------------------------------------
// @c constexpr constructor is required to initialize _something_, it can't be completely uninitializing.
@@ -728,6 +1070,10 @@
this->assign(&addr.sa);
}
+inline IPAddr::IPAddr(string_view const& text) {
+ this->load(text);
+}
+
inline IPAddr &
IPAddr::operator=(in_addr_t addr) {
_family = AF_INET;
@@ -1001,13 +1347,13 @@
}
inline in_port_t
-IPEndpoint::host_order_port(sockaddr const *addr) {
- return ntohs(self_type::port(addr));
+IPEndpoint::host_order_port(sockaddr const *sa) {
+ return ntohs(self_type::port(sa));
}
// --- IPAddr variants ---
-inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(reorder(addr)) {}
+inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {}
inline IP4Addr::IP4Addr(std::string_view const& text) {
if (! this->load(text)) {
@@ -1015,6 +1361,8 @@
}
}
+inline IP4Addr::IP4Addr(IPAddr const& addr) : _addr(addr._family == AF_INET ? addr._addr._ip4._addr : INADDR_ANY) {}
+
inline IP4Addr &
IP4Addr::operator++() {
++_addr;
@@ -1035,10 +1383,6 @@
return _addr;
}
-inline IP4Addr::operator in_addr_t() const {
- return this->network_order();
-}
-
inline auto
IP4Addr::operator=(in_addr_t ip) -> self_type & {
_addr = ntohl(ip);
@@ -1083,6 +1427,10 @@
return *this;
}
+constexpr in_addr_t IP4Addr::reorder(in_addr_t src) {
+ return ((src & 0xFF) << 24) | (((src >> 8) & 0xFF) << 16) | (((src >> 16) & 0xFF) << 8) | ((src >> 24) & 0xFF);
+}
+
// ---
inline IP6Addr::IP6Addr(in6_addr const& addr) {
@@ -1095,6 +1443,8 @@
}
}
+inline IP6Addr::IP6Addr(IPAddr const& addr) : _addr{addr._addr._ip6._addr} {}
+
inline in6_addr& IP6Addr::copy_to(in6_addr & addr) const {
self_type::reorder(addr, _addr._raw);
return addr;
@@ -1165,8 +1515,60 @@
return rhs <= lhs;
}
+// Disambiguating comparisons.
+
+inline bool operator == (IPAddr const& lhs, IP4Addr const& rhs) {
+ return lhs.is_ip4() && static_cast<IP4Addr const&>(lhs) == rhs;
+}
+
+inline bool operator != (IPAddr const& lhs, IP4Addr const& rhs) {
+ return ! lhs.is_ip4() || static_cast<IP4Addr const&>(lhs) != rhs;
+}
+
+inline bool operator == (IP4Addr const& lhs, IPAddr const& rhs) {
+ return rhs.is_ip4() && lhs == static_cast<IP4Addr const&>(rhs);
+}
+
+inline bool operator != (IP4Addr const& lhs, IPAddr const& rhs) {
+ return ! rhs.is_ip4() || lhs != static_cast<IP4Addr const&>(rhs);
+}
+
+inline bool operator == (IPAddr const& lhs, IP6Addr const& rhs) {
+ return lhs.is_ip6() && static_cast<IP6Addr const&>(lhs) == rhs;
+}
+
+inline bool operator != (IPAddr const& lhs, IP6Addr const& rhs) {
+ return ! lhs.is_ip6() || static_cast<IP6Addr const&>(lhs) != rhs;
+}
+
+inline bool operator == (IP6Addr const& lhs, IPAddr const& rhs) {
+ return rhs.is_ip6() && lhs == static_cast<IP6Addr const&>(rhs);
+}
+
+inline bool operator != (IP6Addr const& lhs, IPAddr const& rhs) {
+ return ! rhs.is_ip6() || lhs != static_cast<IP6Addr const&>(rhs);
+}
+
// +++ IPRange +++
+inline IP4Range::IP4Range(string_view const& text) {
+ this->load(text);
+}
+
+inline IPRange::IPRange(IP4Range const& range) : _family(AF_INET) {
+ _range._ip4 = range;
+}
+
+inline IPRange::IPRange(IP6Range const& range) : _family(AF_INET6) {
+ _range._ip6 = range;
+}
+
+inline IPRange::IPRange(string_view const& text) {
+ this->load(text);
+}
+
+inline bool IPRange::is(sa_family_t family) const { return family == _family; }
+
// +++ IpMask +++
inline IPMask::IPMask(raw_type width, sa_family_t family) : _mask(width), _family(family) {}
@@ -1215,12 +1617,16 @@
// --- IPSpace
-template < typename PAYLOAD > auto IPSpace<PAYLOAD>::mark(swoc::IP4Range const &r, PAYLOAD const &payload) -> self_type & {
- _ip4.mark(r, payload);
+template < typename PAYLOAD > auto IPSpace<PAYLOAD>::mark(IPRange const &range, PAYLOAD const &payload) -> self_type & {
+ if (range.is(AF_INET)) {
+ _ip4.mark(range, payload);
+ } else if (range.is(AF_INET6)) {
+ _ip6.mark(range, payload);
+ }
return *this;
}
-template < typename PAYLOAD > auto IPSpace<PAYLOAD>::fill(swoc::IPRange const &range, PAYLOAD const &payload) -> self_type & {
+template < typename PAYLOAD > auto IPSpace<PAYLOAD>::fill(IPRange const &range, PAYLOAD const &payload) -> self_type & {
if (range.is(AF_INET6)) {
_ip6.fill(range, payload);
} else if (range.is(AF_INET)) {
diff --git a/swoc++/src/bw_ip_format.cc b/swoc++/src/bw_ip_format.cc
index 14f5c27..a87969c 100644
--- a/swoc++/src/bw_ip_format.cc
+++ b/swoc++/src/bw_ip_format.cc
@@ -49,40 +49,6 @@
using bwf::Spec;
BufferWriter &
-bwformat(BufferWriter &w, Spec const &spec, IP4Addr const& addr)
-{
- in_addr_t host = addr.host_order();
- Spec local_spec{spec}; // Format for address elements.
- bool align_p = false;
-
- if (spec._ext.size()) {
- if (spec._ext.front() == '=') {
- align_p = true;
- local_spec._fill = '0';
- } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
- align_p = true;
- local_spec._fill = spec._ext[0];
- }
- }
-
- if (align_p) {
- local_spec._min = 3;
- local_spec._align = Spec::Align::RIGHT;
- } else {
- local_spec._min = 0;
- }
-
- bwformat(w, local_spec, static_cast<uint8_t>(host >> 24 & 0xFF));
- w.write('.');
- bwformat(w, local_spec, static_cast<uint8_t>(host >> 16 & 0xFF));
- w.write('.');
- bwformat(w, local_spec, static_cast<uint8_t>(host >> 8 & 0xFF));
- w.write('.');
- bwformat(w, local_spec, static_cast<uint8_t>(host & 0xFF));
- return w;
-}
-
-BufferWriter &
bwformat(BufferWriter &w, Spec const &spec, in6_addr const &addr)
{
using QUAD = uint16_t const;
@@ -151,6 +117,136 @@
}
BufferWriter &
+bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr)
+{
+ Spec local_spec{spec}; // Format for address elements and port.
+ bool port_p{true};
+ bool addr_p{true};
+ bool family_p{false};
+ bool local_numeric_fill_p{false};
+ char local_numeric_fill_char{'0'};
+
+ if (spec._type == 'p' || spec._type == 'P') {
+ bwformat(w, spec, static_cast<void const *>(addr));
+ return w;
+ }
+
+ if (spec._ext.size()) {
+ if (spec._ext.front() == '=') {
+ local_numeric_fill_p = true;
+ local_spec._ext.remove_prefix(1);
+ } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
+ local_numeric_fill_p = true;
+ local_numeric_fill_char = spec._ext.front();
+ local_spec._ext.remove_prefix(2);
+ }
+ }
+ if (local_spec._ext.size()) {
+ addr_p = port_p = false;
+ for (char c : local_spec._ext) {
+ switch (c) {
+ case 'a':
+ case 'A':
+ addr_p = true;
+ break;
+ case 'p':
+ case 'P':
+ port_p = true;
+ break;
+ case 'f':
+ case 'F':
+ family_p = true;
+ break;
+ }
+ }
+ }
+
+ if (addr_p) {
+ bool bracket_p = false;
+ switch (addr->sa_family) {
+ case AF_INET:
+ bwformat(w, spec, IP4Addr{IP4Addr::reorder(reinterpret_cast<sockaddr_in const *>(addr)->sin_addr.s_addr)});
+ break;
+ case AF_INET6:
+ if (port_p) {
+ w.write('[');
+ bracket_p = true; // take a note - put in the trailing bracket.
+ }
+ bwformat(w, spec, reinterpret_cast<sockaddr_in6 const *>(addr)->sin6_addr);
+ break;
+ default:
+ w.print("*Not IP address [{}]*", addr->sa_family);
+ break;
+ }
+ if (bracket_p)
+ w.write(']');
+ if (port_p)
+ w.write(':');
+ }
+ if (port_p) {
+ if (local_numeric_fill_p) {
+ local_spec._min = 5;
+ local_spec._fill = local_numeric_fill_char;
+ local_spec._align = Spec::Align::RIGHT;
+ } else {
+ local_spec._min = 0;
+ }
+ bwformat(w, local_spec, static_cast<uintmax_t>(IPEndpoint::host_order_port(addr)));
+ }
+ if (family_p) {
+ local_spec._min = 0;
+ if (addr_p || port_p)
+ w.write(' ');
+ if (spec.has_numeric_type()) {
+ bwformat(w, local_spec, static_cast<uintmax_t>(addr->sa_family));
+ } else {
+ swoc::bwformat(w, local_spec, IPEndpoint::family_name(addr->sa_family));
+ }
+ }
+ return w;
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, Spec const &spec, IP4Addr const& addr)
+{
+ in_addr_t host = addr.host_order();
+ Spec local_spec{spec}; // Format for address elements.
+ bool align_p = false;
+
+ if (spec._ext.size()) {
+ if (spec._ext.front() == '=') {
+ align_p = true;
+ local_spec._fill = '0';
+ } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
+ align_p = true;
+ local_spec._fill = spec._ext[0];
+ }
+ }
+
+ if (align_p) {
+ local_spec._min = 3;
+ local_spec._align = Spec::Align::RIGHT;
+ } else {
+ local_spec._min = 0;
+ }
+
+ bwformat(w, local_spec, static_cast<uint8_t>(host >> 24 & 0xFF));
+ w.write('.');
+ bwformat(w, local_spec, static_cast<uint8_t>(host >> 16 & 0xFF));
+ w.write('.');
+ bwformat(w, local_spec, static_cast<uint8_t>(host >> 8 & 0xFF));
+ w.write('.');
+ bwformat(w, local_spec, static_cast<uint8_t>(host & 0xFF));
+ return w;
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, Spec const &spec, IP6Addr const& addr)
+{
+ return bwformat(w, spec, addr.network_order());
+}
+
+BufferWriter &
bwformat(BufferWriter &w, Spec const &spec, IPAddr const &addr)
{
Spec local_spec{spec}; // Format for address elements and port.
@@ -182,7 +278,7 @@
if (addr_p) {
if (addr.is_ip4()) {
- swoc::bwformat(w, spec, addr.network_ip4());
+ swoc::bwformat(w, spec, static_cast<IP4Addr const&>(addr));
} else if (addr.is_ip6()) {
swoc::bwformat(w, spec, addr.network_ip6());
} else {
@@ -205,93 +301,28 @@
}
BufferWriter &
-bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr)
-{
- Spec local_spec{spec}; // Format for address elements and port.
- bool port_p{true};
- bool addr_p{true};
- bool family_p{false};
- bool local_numeric_fill_p{false};
- char local_numeric_fill_char{'0'};
-
- if (spec._type == 'p' || spec._type == 'P') {
- bwformat(w, spec, static_cast<void const *>(addr));
- return w;
- }
-
- if (spec._ext.size()) {
- if (spec._ext.front() == '=') {
- local_numeric_fill_p = true;
- local_spec._ext.remove_prefix(1);
- } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
- local_numeric_fill_p = true;
- local_numeric_fill_char = spec._ext.front();
- local_spec._ext.remove_prefix(2);
- }
- }
- if (local_spec._ext.size()) {
- addr_p = port_p = false;
- for (char c : local_spec._ext) {
- switch (c) {
- case 'a':
- case 'A':
- addr_p = true;
- break;
- case 'p':
- case 'P':
- port_p = true;
- break;
- case 'f':
- case 'F':
- family_p = true;
- break;
- }
- }
- }
-
- if (addr_p) {
- bool bracket_p = false;
- switch (addr->sa_family) {
- case AF_INET:
- bwformat(w, spec, IP4Addr{reinterpret_cast<sockaddr_in const *>(addr)->sin_addr.s_addr});
- break;
- case AF_INET6:
- if (port_p) {
- w.write('[');
- bracket_p = true; // take a note - put in the trailing bracket.
- }
- bwformat(w, spec, reinterpret_cast<sockaddr_in6 const *>(addr)->sin6_addr);
- break;
- default:
- w.print("*Not IP address [{}]*", addr->sa_family);
- break;
- }
- if (bracket_p)
- w.write(']');
- if (port_p)
- w.write(':');
- }
- if (port_p) {
- if (local_numeric_fill_p) {
- local_spec._min = 5;
- local_spec._fill = local_numeric_fill_char;
- local_spec._align = Spec::Align::RIGHT;
- } else {
- local_spec._min = 0;
- }
- bwformat(w, local_spec, static_cast<uintmax_t>(IPEndpoint::host_order_port(addr)));
- }
- if (family_p) {
- local_spec._min = 0;
- if (addr_p || port_p)
- w.write(' ');
- if (spec.has_numeric_type()) {
- bwformat(w, local_spec, static_cast<uintmax_t>(addr->sa_family));
- } else {
- swoc::bwformat(w, local_spec, IPEndpoint::family_name(addr->sa_family));
- }
- }
- return w;
+bwformat(BufferWriter & w, Spec const& spec, IP4Range const& range) {
+ return range.is_empty()
+ ? w.write("*-*"_tv)
+ : w.print("{}-{}", range.min(), range.max());
}
+BufferWriter &
+bwformat(BufferWriter & w, Spec const& spec, IP6Range const& range) {
+ return range.is_empty()
+ ? w.write("*-*"_tv)
+ : w.print("{}-{}", range.min(), range.max());
+}
+
+BufferWriter &
+bwformat(BufferWriter & w, Spec const& spec, IPRange const& range) {
+ return range.is(AF_INET)
+ ? bwformat(w, spec, static_cast<IP4Range const&>(range))
+ : range.is(AF_INET6)
+ ? bwformat(w, spec, static_cast<IP6Range const&>(range))
+ : w.write("*-*"_tv)
+ ;
+}
+
+
} // namespace swoc
diff --git a/swoc++/src/swoc_ip.cc b/swoc++/src/swoc_ip.cc
index 076db6b..1c1675e 100644
--- a/swoc++/src/swoc_ip.cc
+++ b/swoc++/src/swoc_ip.cc
@@ -27,26 +27,23 @@
using swoc::svtou;
using namespace swoc::literals;
-namespace
-{
+namespace {
// Handle the @c sin_len member, the presence of which varies across compilation environments.
-template <typename T>
+template<typename T>
auto
-Set_Sockaddr_Len_Case(T *, swoc::meta::CaseTag<0>) -> decltype(swoc::meta::TypeFunc<void>())
-{
+Set_Sockaddr_Len_Case(T *, swoc::meta::CaseTag<0>) -> decltype(swoc::meta::TypeFunc<void>()) {
}
-template <typename T>
+template<typename T>
auto
-Set_Sockaddr_Len_Case(T *addr, swoc::meta::CaseTag<1>) -> decltype(T::sin_len, swoc::meta::TypeFunc<void>())
-{
+Set_Sockaddr_Len_Case(T *addr
+ , swoc::meta::CaseTag<1>) -> decltype(T::sin_len, swoc::meta::TypeFunc<void>()) {
addr->sin_len = sizeof(T);
}
-template <typename T>
+template<typename T>
void
-Set_Sockaddr_Len(T *addr)
-{
+Set_Sockaddr_Len(T *addr) {
Set_Sockaddr_Len_Case(addr, swoc::meta::CaseArg);
}
@@ -197,15 +194,15 @@
IPAddr::fill(sockaddr *sa, in_port_t port) const {
switch (sa->sa_family = _family) {
case AF_INET: {
- sockaddr_in *sa4{reinterpret_cast<sockaddr_in *>(sa)};
+ auto sa4 = reinterpret_cast<sockaddr_in *>(sa);
memset(sa4, 0, sizeof(*sa4));
- sa4->sin_addr.s_addr = _addr._ip4;
+ sa4->sin_addr.s_addr = _addr._ip4.network_order();
sa4->sin_port = port;
Set_Sockaddr_Len(sa4);
}
break;
case AF_INET6: {
- sockaddr_in6 *sa6{reinterpret_cast<sockaddr_in6 *>(sa)};
+ auto sa6 = reinterpret_cast<sockaddr_in6 *>(sa);
memset(sa6, 0, sizeof(*sa6));
sa6->sin6_port = port;
Set_Sockaddr_Len(sa6);
@@ -293,6 +290,18 @@
return false;
}
+IP4Addr::IP4Addr(sockaddr_in const *sa) : _addr(reorder(sa->sin_addr.s_addr)) {}
+
+auto IP4Addr::operator=(sockaddr_in const *sa) -> self_type& {
+ _addr = reorder(sa->sin_addr.s_addr);
+ return *this;
+}
+
+sockaddr_in *IP4Addr::fill(sockaddr_in *sa, in_port_t port) const {
+ sa->sin_addr.s_addr = this->network_order();
+ return sa;
+}
+
bool
IP6Addr::load(std::string_view const&str) {
TextView src{str};
@@ -559,22 +568,22 @@
return false;
}
-IP6Range & IP6Range::assign(IP6Addr const&addr, IPMask const&mask) {
- static constexpr auto FULL_MASK { std::numeric_limits<uint64_t>::max() };
+IP6Range&IP6Range::assign(IP6Addr const&addr, IPMask const&mask) {
+ static constexpr auto FULL_MASK{std::numeric_limits<uint64_t>::max()};
auto cidr = mask.width();
if (cidr == 0) {
_min = metric_type::MIN;
_max = metric_type::MAX;
} else if (cidr < 64) { // only upper bytes affected, lower bytes are forced.
- auto bits = FULL_MASK << (64 - cidr);
- _min._addr._u64[0] = addr._addr._u64[0] & bits;
- _min._addr._u64[1] = 0;
- _max._addr._u64[0] = addr._addr._u64[0] | ~bits;
- _max._addr._u64[1] = FULL_MASK;
+ auto bits = FULL_MASK << (64 - cidr);
+ _min._addr._u64[0] = addr._addr._u64[0] & bits;
+ _min._addr._u64[1] = 0;
+ _max._addr._u64[0] = addr._addr._u64[0] | ~bits;
+ _max._addr._u64[1] = FULL_MASK;
} else if (cidr == 64) {
_min._addr._u64[0] = _max._addr._u64[0] = addr._addr._u64[0];
- _min._addr._u64[1] = 0;
- _max._addr._u64[1] = FULL_MASK;
+ _min._addr._u64[1] = 0;
+ _max._addr._u64[1] = FULL_MASK;
} else if (cidr <= 128) { // _min bytes changed, _max bytes unaffected.
_min = _max = addr;
if (cidr < 128) {
@@ -613,7 +622,7 @@
return false;
}
-bool IPRange::load(std::string_view const& text) {
+bool IPRange::load(std::string_view const&text) {
static const string_view CHARS{".:"};
auto idx = text.find_first_of(CHARS);
if (idx != text.npos) {
@@ -633,7 +642,7 @@
}
IPAddr IPRange::min() const {
- switch(_family) {
+ switch (_family) {
case AF_INET: return _range._ip4.min();
case AF_INET6: return _range._ip6.min();
default: break;
@@ -642,7 +651,7 @@
}
IPAddr IPRange::max() const {
- switch(_family) {
+ switch (_family) {
case AF_INET: return _range._ip4.max();
case AF_INET6: return _range._ip6.max();
default: break;
diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc
index 285e13f..b210666 100644
--- a/unit_tests/test_ip.cc
+++ b/unit_tests/test_ip.cc
@@ -19,11 +19,16 @@
using namespace swoc::literals;
using swoc::TextView;
using swoc::IPEndpoint;
+
using swoc::IP4Addr;
using swoc::IP4Range;
+
using swoc::IP6Addr;
using swoc::IP6Range;
+using swoc::IPAddr;
+using swoc::IPRange;
+
TEST_CASE("ink_inet", "[libswoc][ip]") {
// Use TextView because string_view(nullptr) fails. Gah.
struct ip_parse_spec {
@@ -82,6 +87,10 @@
IP4Addr a4_1{"172.28.56.33"};
IP4Addr a4_2{"172.28.56.34"};
IP4Addr a4_3{"170.28.56.35"};
+ IP4Addr a4_loopback{"127.0.0.1"_tv};
+ IP4Addr ip4_loopback{INADDR_LOOPBACK};
+
+ REQUIRE(a4_loopback == ip4_loopback);
REQUIRE(a4_1 != a4_null);
REQUIRE(a4_1 != a4_2);
@@ -150,8 +159,16 @@
std::string_view localhost{"[::1]:8080"};
swoc::LocalBufferWriter<1024> w;
+ REQUIRE(ep.parse(addr_null) == true);
+ w.clear().print("{::a}", ep);
+ REQUIRE(w.view() == "::");
+
+ ep.set_to_loopback(AF_INET6);
+ w.clear().print("{::a}", ep);
+ REQUIRE(w.view() == "::1");
+
REQUIRE(ep.parse(addr_1) == true);
- w.print("{}", ep);
+ w.clear().print("{}", ep);
REQUIRE(w.view() == addr_1);
w.clear().print("{::p}", ep);
REQUIRE(w.view() == "8080");
@@ -166,30 +183,6 @@
w.clear().print("{:: =a}", ep);
REQUIRE(w.view() == "ffee: 0: 0: 0:24c3:3349:3cee: 143");
-#if 0
- ep.setToLoopback(AF_INET6);
- w.reset().print("{::a}", ep);
- REQUIRE(w.view() == "::1");
- REQUIRE(0 == ats_ip_pton(addr_3, &ep.sa));
- w.reset().print("{::a}", ep);
- REQUIRE(w.view() == "1337:ded:beef::");
- REQUIRE(0 == ats_ip_pton(addr_4, &ep.sa));
- w.reset().print("{::a}", ep);
- REQUIRE(w.view() == "1337::ded:beef");
-
- REQUIRE(0 == ats_ip_pton(addr_5, &ep.sa));
- w.reset().print("{:X:a}", ep);
- REQUIRE(w.view() == "1337::DED:BEEF:0:0:956");
-
- REQUIRE(0 == ats_ip_pton(addr_6, &ep.sa));
- w.reset().print("{::a}", ep);
- REQUIRE(w.view() == "1337:0:0:ded:beef::");
-
- REQUIRE(0 == ats_ip_pton(addr_null, &ep.sa));
- w.reset().print("{::a}", ep);
- REQUIRE(w.view() == "::");
-#endif
-
REQUIRE(ep.parse(addr_2) == true);
w.clear().print("{::a}", ep);
REQUIRE(w.view() == addr_2.substr(0, 13));
@@ -212,40 +205,47 @@
w.clear().print("{::=a}", ep);
REQUIRE(w.view() == "172.017.099.231");
-#if 0
+ REQUIRE(ep.parse(addr_3) == true);
+ w.clear().print("{::a}", ep);
+ REQUIRE(w.view() == "1337:ded:beef::"_tv);
+
+ REQUIRE(ep.parse(addr_4) == true);
+ w.clear().print("{::a}", ep);
+ REQUIRE(w.view() == "1337::ded:beef"_tv);
+
+ REQUIRE(ep.parse(addr_5) == true);
+ w.clear().print("{:X:a}", ep);
+ REQUIRE(w.view() == "1337::DED:BEEF:0:0:956");
+
+ REQUIRE(ep.parse(addr_6) == true);
+ w.clear().print("{::a}", ep);
+ REQUIRE(w.view() == "1337:0:0:ded:beef::");
+
// Documentation examples
- REQUIRE(0 == ats_ip_pton(addr_7, &ep.sa));
- w.reset().print("To {}", ep);
+ REQUIRE(ep.parse(addr_7) == true);
+ w.clear().print("To {}", ep);
REQUIRE(w.view() == "To 172.19.3.105:4951");
- w.reset().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice.
+ w.clear().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice.
REQUIRE(w.view() == "To 172.19.3.105 on port 4951");
- w.reset().print("To {::=}", ep);
+ w.clear().print("To {::=}", ep);
REQUIRE(w.view() == "To 172.019.003.105:04951");
- w.reset().print("{::a}", ep);
+ w.clear().print("{::a}", ep);
REQUIRE(w.view() == "172.19.3.105");
- w.reset().print("{::=a}", ep);
+ w.clear().print("{::=a}", ep);
REQUIRE(w.view() == "172.019.003.105");
- w.reset().print("{::0=a}", ep);
+ w.clear().print("{::0=a}", ep);
REQUIRE(w.view() == "172.019.003.105");
- w.reset().print("{:: =a}", ep);
+ w.clear().print("{:: =a}", ep);
REQUIRE(w.view() == "172. 19. 3.105");
- w.reset().print("{:>20:a}", ep);
+ w.clear().print("{:>20:a}", ep);
REQUIRE(w.view() == " 172.19.3.105");
- w.reset().print("{:>20:=a}", ep);
+ w.clear().print("{:>20:=a}", ep);
REQUIRE(w.view() == " 172.019.003.105");
- w.reset().print("{:>20: =a}", ep);
+ w.clear().print("{:>20: =a}", ep);
REQUIRE(w.view() == " 172. 19. 3.105");
- w.reset().print("{:<20:a}", ep);
+ w.clear().print("{:<20:a}", ep);
REQUIRE(w.view() == "172.19.3.105 ");
- w.reset().print("{:p}", reinterpret_cast<sockaddr const *>(0x1337beef));
- REQUIRE(w.view() == "0x1337beef");
-
- ats_ip_pton(addr_1, &ep.sa);
- w.reset().print("{}", swoc::bwf::Hex_Dump(ep));
- REQUIRE(w.view() == "ffee00000000000024c333493cee0143");
-#endif
-
REQUIRE(ep.parse(localhost) == true);
w.clear().print("{}", ep);
REQUIRE(w.view() == localhost);
@@ -317,17 +317,17 @@
TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") {
using int_space = swoc::IPSpace<unsigned>;
int_space space;
- auto dump = [] (int_space & space) -> void {
+ auto dump = [](int_space&space) -> void {
swoc::LocalBufferWriter<1024> w;
std::cout << "Dumping " << space.count() << " ranges" << std::endl;
- for ( auto & r : space ) {
- std::cout << w.clear().print("{} - {} : {}\n", r.min(), r.max(), r.payload()).view();
+ for (auto &&[r, payload] : space) {
+ std::cout << w.clear().print("{} - {} : {}\n", r.min(), r.max(), payload).view();
}
};
REQUIRE(space.count() == 0);
- space.mark({IP4Addr("172.16.0.0"), IP4Addr("172.16.0.255")}, 1);
+ space.mark(IPRange{{IP4Addr("172.16.0.0"), IP4Addr("172.16.0.255")}}, 1);
auto result = space.find({"172.16.0.97"});
REQUIRE(result != nullptr);
REQUIRE(*result == 1);
@@ -335,7 +335,7 @@
result = space.find({"172.17.0.97"});
REQUIRE(result == nullptr);
- space.mark({IP4Addr("172.16.0.12"), IP4Addr("172.16.0.25")}, 2);
+ space.mark(IPRange{"172.16.0.12-172.16.0.25"_tv}, 2);
result = space.find({"172.16.0.21"});
REQUIRE(result != nullptr);
@@ -343,12 +343,19 @@
REQUIRE(space.count() == 3);
space.clear();
- auto BF = [](unsigned&lhs, unsigned rhs) -> bool { lhs |= rhs; return true; };
+ auto BF = [](unsigned&lhs, unsigned rhs) -> bool {
+ lhs |= rhs;
+ return true;
+ };
unsigned *payload;
swoc::IP4Range r_1{"1.1.1.0-1.1.1.9"};
swoc::IP4Range r_2{"1.1.2.0-1.1.2.97"};
swoc::IP4Range r_3{"1.1.0.0-1.2.0.0"};
+ // Compiler check - make sure both of these work.
+ REQUIRE(r_1.min() == IP4Addr("1.1.1.0"_tv));
+ REQUIRE(r_1.max() == IPAddr("1.1.1.9"_tv));
+
space.blend(r_1, 0x1, BF);
REQUIRE(space.count() == 1);
REQUIRE(nullptr == space.find(r_2.min()));
@@ -366,7 +373,6 @@
REQUIRE(*payload == 0x2);
space.blend(r_3, 0x4, BF);
- dump(space);
REQUIRE(space.count() == 5);
payload = space.find(r_2.min());
REQUIRE(payload != nullptr);
@@ -381,11 +387,109 @@
REQUIRE(*payload == 0x5);
space.blend({r_2.min(), r_3.max()}, 0x6, BF);
- dump(space);
REQUIRE(space.count() == 4);
}
-#if 1
+TEST_CASE("IPSpace bitset", "[libswoc][ipspace][bitset]") {
+ using PAYLOAD = std::bitset<32>;
+ using Space = swoc::IPSpace<PAYLOAD>;
+
+ auto dump = [](Space&space) -> void {
+ swoc::LocalBufferWriter<1024> w;
+ std::cout << "Dumping " << space.count() << " ranges" << std::endl;
+ for (auto &&[r, payload] : space) {
+ w.clear().print("{}-{} :", r.min(), r.max());
+ std::cout << w << payload << std::endl;
+ }
+ };
+ auto reverse_dump = [](Space&space) -> void {
+ swoc::LocalBufferWriter<1024> w;
+ std::cout << "Dumping " << space.count() << " ranges" << std::endl;
+ for (auto spot = space.end(); spot != space.begin();) {
+ auto &&[r, payload]{*--spot};
+ w.clear().print("{} :", r);
+ std::cout << w << payload << std::endl;
+ }
+ };
+
+ std::array<std::tuple<TextView, std::initializer_list<unsigned>>, 6> ranges = {
+ {
+ {"172.28.56.12-172.28.56.99"_tv, {0, 2, 3}}
+ , {"10.10.35.0/24"_tv, {1, 2}}
+ , {"192.168.56.0/25"_tv, {10, 12, 31}}
+ , {"1337::ded:beef-1337::ded:ceef"_tv, {4, 5, 6, 7}}
+ , {"ffee:1f2d:c587:24c3:9128:3349:3cee:143-ffee:1f2d:c587:24c3:9128:3349:3cFF:FFFF"_tv, {9, 10, 18}}
+ , {"10.12.148.0/23"_tv, {1, 2, 17}}
+ }};
+
+ Space space;
+
+ for (auto &&[text, bit_list] : ranges) {
+ PAYLOAD bits;
+ for (auto bit : bit_list) {
+ bits[bit] = true;
+ }
+ space.mark(IPRange{text}, bits);
+ }
+ REQUIRE(space.count() == ranges.size());
+ dump(space);
+ reverse_dump(space);
+}
+
+TEST_CASE("IPSpace docJJ", "[libswoc][ipspace][docJJ]") {
+ using PAYLOAD = std::bitset<32>;
+ using Space = swoc::IPSpace<PAYLOAD>;
+
+ auto dump = [](Space&space) -> void {
+ swoc::LocalBufferWriter<1024> w;
+ std::cout << "Dumping " << space.count() << " ranges" << std::endl;
+ for (auto &&[r, payload] : space) {
+ w.clear().print("{}-{} :", r.min(), r.max());
+ std::cout << w << payload << std::endl;
+ }
+ };
+ auto reverse_dump = [](Space&space) -> void {
+ swoc::LocalBufferWriter<1024> w;
+ std::cout << "Dumping " << space.count() << " ranges" << std::endl;
+ for (auto spot = space.end(); spot != space.begin();) {
+ auto &&[r, payload]{*--spot};
+ w.clear().print("{} :", r);
+ std::cout << w << payload << std::endl;
+ }
+ };
+
+ auto blender = [](PAYLOAD& lhs, PAYLOAD const& rhs) -> bool {
+ lhs |= rhs;
+ return true;
+ };
+
+ std::array<std::tuple<TextView, std::initializer_list<unsigned>>, 9> ranges = {
+ {
+ { "100.0.0.0-100.0.0.255", { 0 } }
+ , { "100.0.1.0-100.0.1.255", { 1 } }
+ , { "100.0.2.0-100.0.2.255", { 2 } }
+ , { "100.0.3.0-100.0.3.255", { 3 } }
+ , { "100.0.4.0-100.0.4.255", { 4 } }
+ , { "100.0.5.0-100.0.5.255", { 5 } }
+ , { "100.0.6.0-100.0.6.255", { 6 } }
+ , { "100.0.0.0-100.0.0.255", { 31 } }
+ , { "100.0.1.0-100.0.1.255", { 30 } }
+ }};
+
+ Space space;
+
+ for (auto &&[text, bit_list] : ranges) {
+ PAYLOAD bits;
+ for (auto bit : bit_list) {
+ bits[bit] = true;
+ }
+ space.blend(IPRange{text}, bits, blender);
+ }
+ dump(space);
+ reverse_dump(space);
+}
+
+#if 0
TEST_CASE("IP Space YNETDB", "[libswoc][ipspace][ynetdb]") {
std::set<std::string_view> Locations;
std::set<std::string_view> Owners;