This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch dev-1-0-11 in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git
commit 3d8cd17db1c48a8eb8939e66022b47723cada1ac Author: Alan M. Carroll <[email protected]> AuthorDate: Fri Jan 31 07:48:26 2020 -0600 IPSpace fixes. --- swoc++/include/swoc/DiscreteRange.h | 183 ++++++++----- swoc++/include/swoc/IntrusiveDList.h | 43 ++- swoc++/include/swoc/bwf_ip.h | 50 +++- swoc++/include/swoc/swoc_ip.h | 494 +++++++++++++++++++++++++++++++---- swoc++/src/bw_ip_format.cc | 235 +++++++++-------- swoc++/src/swoc_ip.cc | 61 +++-- unit_tests/test_ip.cc | 214 +++++++++++---- 7 files changed, 986 insertions(+), 294 deletions(-) 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 } // 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 @@ enum DiscreteRangeRelation { 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 @@ protected: 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 @@ public: * * @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 @@ public: */ 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 @@ public: */ 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 @@ DiscreteRange<T>::hull(DiscreteRange::self_type const &that) const { 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 @@ DiscreteRange<T>::is_superset_of(DiscreteRange::self_type const &that) const { 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 @@ DiscreteRange<T>::is_adjacent_to(DiscreteRange::self_type const &that) const { 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 @@ protected: */ 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 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c // 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 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c // 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; + // Always look back at prev, so if there's no overlap at all, skip it. + if (n->max() < remaining.min()) { + n = next(n); + continue; + } - 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; - } + // 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; } - // 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; + + 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; - - // Check for no right overlap - that means the next node is past the target range. + // @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 @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 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c 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 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c 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 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c 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 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c // 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 @@ public: /// 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 @@ IntrusiveDList<L>::const_iterator::operator!=(self_type const &that) const { 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 @@ namespace swoc // 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, 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, sockaddr 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 @@ union IPEndpoint { /// @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 @@ public: 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 @@ public: /// 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 @@ public: 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 @@ public: /// 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 @@ public: /// 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 @@ public: /// 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 @@ protected: 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 @@ public: 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 @@ public: 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 @@ public: protected: friend bool operator==(self_type const &, self_type const &); + friend IP4Addr; + friend IP6Addr; /// Address data. union raw_addr_type { @@ -457,7 +507,7 @@ public: 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 @@ class IP4Range : public DiscreteRange<IP4Addr> { 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 @@ public: * 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 @@ class IP6Range : public DiscreteRange<IP6Addr> { 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 @@ public: 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 @@ public: */ 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 @@ public: * * 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 @@ public: /// 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 @@ inline IPAddr::IPAddr(IPEndpoint const &addr) { 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 @@ IPEndpoint::port(sockaddr const *addr) { } 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(std::string_view const& text) { } } +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 @@ inline in_addr_t IP4Addr::host_order() const { 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 @@ inline IP4Addr & IP4Addr::operator|=(IPMask const& mask) { 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(std::string_view const& text) { } } +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 @@ inline bool operator >= (IP6Addr const& lhs, IP6Addr const& rhs) { 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 @@ IpNet::mask() const { // --- 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 @@ -48,40 +48,6 @@ namespace swoc { 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) { @@ -151,96 +117,157 @@ bwformat(BufferWriter &w, Spec const &spec, in6_addr const &addr) } BufferWriter & -bwformat(BufferWriter &w, Spec const &spec, IPAddr const &addr) +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 = false; + addr_p = port_p = false; for (char c : local_spec._ext) { switch (c) { - case 'a': - case 'A': - addr_p = true; - break; - case 'f': - case 'F': - family_p = true; - break; + 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) { - if (addr.is_ip4()) { - swoc::bwformat(w, spec, addr.network_ip4()); - } else if (addr.is_ip6()) { - swoc::bwformat(w, spec, addr.network_ip6()); + 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 { - w.print("*Not IP address [{}]*", addr.family()); + 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) { + if (addr_p || port_p) w.write(' '); - } if (spec.has_numeric_type()) { - bwformat(w, local_spec, static_cast<uintmax_t>(addr.family())); + bwformat(w, local_spec, static_cast<uintmax_t>(addr->sa_family)); } else { - swoc::bwformat(w, local_spec, addr.family()); + swoc::bwformat(w, local_spec, IPEndpoint::family_name(addr->sa_family)); } } return w; } BufferWriter & -bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr) +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. - 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; + addr_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; @@ -250,48 +277,52 @@ bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr) } 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; + if (addr.is_ip4()) { + swoc::bwformat(w, spec, static_cast<IP4Addr const&>(addr)); + } else if (addr.is_ip6()) { + swoc::bwformat(w, spec, addr.network_ip6()); } else { - local_spec._min = 0; + w.print("*Not IP address [{}]*", addr.family()); } - 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) + if (addr_p) { w.write(' '); + } if (spec.has_numeric_type()) { - bwformat(w, local_spec, static_cast<uintmax_t>(addr->sa_family)); + bwformat(w, local_spec, static_cast<uintmax_t>(addr.family())); } else { - swoc::bwformat(w, local_spec, IPEndpoint::family_name(addr->sa_family)); + swoc::bwformat(w, local_spec, addr.family()); } } return w; } +BufferWriter & +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::svtoi; 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 @@ sockaddr * 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 @@ IP4Addr::load(std::string_view const&text) { 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 @@ bool IP4Range::load(string_view text) { 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 @@ bool IP6Range::load(std::string_view text) { 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 @@ bool IPRange::load(std::string_view const& text) { } 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::min() const { } 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 std::literals; 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 @@ TEST_CASE("ink_inet", "[libswoc][ip]") { 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 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") { 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 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") { 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 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") { 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 @@ Property *PropertyGroup::operator[](std::string_view const&name) { 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 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { 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 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { 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 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { 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 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { 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;
