diff --git a/code/include/swoc/DiscreteRange.h b/code/include/swoc/DiscreteRange.h index 80da09c..56e8e68 100644 --- a/code/include/swoc/DiscreteRange.h +++ b/code/include/swoc/DiscreteRange.h @@ -163,6 +163,16 @@ template class DiscreteRange { */ metric_type const& max() const; + /// Test for equality. + bool operator == (self_type const& that) const { + return _min == that._min && _max == that._max; + } + + /// Test for inequality. + bool operator != (self_type const& that) const { + return _min != that._min | _max != that._max; + } + /** Check if a value is in @a this range. * * @param m Metric value to check. @@ -971,6 +981,37 @@ DiscreteSpace::insert_after(DiscreteSpace::Node *spot, Discrete _root = static_cast(node->rebalance_after_insert()); } +template +DiscreteSpace& +DiscreteSpace::erase(DiscreteSpace::range_type const& range) { + Node *n = this->lower_bound(range.min()); // current node. + while (n) { + auto nn = next(n); // cache in case @a n disappears. + if (n->min() > range.max()) { // cleared the target range, done. + break; + } + + if (n->max() >= range.min()) { // some overlap + if (n->max() <= range.max()) { // pure left overlap, clip. + if (n->min() >= range.min()) { // covered, remove. + this->remove(n); + } else { // stub on the left, clip to that. + n->assign_max(--metric_type{range.min()}); + } + } else if (n->min() >= range.min()) { // pure left overlap, clip. + n->assign_min(++metric_type{range.max()}); + } else { // @a n covers @a range, must split. + auto y = _fa.make(range_type{n->min(), --metric_type{range.min()}}, n->payload()); + n->assign_min(++metric_type{range.max()}); + this->insert_before(n, y); + break; + } + } + n = nn; + } + return *this; +} + template DiscreteSpace& DiscreteSpace::mark(DiscreteSpace::range_type const& range @@ -979,16 +1020,15 @@ DiscreteSpace::mark(DiscreteSpace::range_type const& range Node *x = nullptr; // New node, gets set if we re-use an existing one. Node *y = nullptr; // Temporary for removing and advancing. - METRIC max_plus_1 = range.max(); - ++max_plus_1; // careful to use this only in places there's no chance of overflow. + // Use carefully, only in situations where it is known there is no overflow. + auto max_plus_1 = ++metric_type{range.max()}; /* We have lots of special cases here primarily to minimize memory allocation by re-using an existing node as often as possible. */ if (n) { // Watch for wrap. - METRIC min_minus_1 = range.min(); - --min_minus_1; + auto min_minus_1 = --metric_type{range.min()}; if (n->min() == range.min()) { // Could be another span further left which is adjacent. // Coalesce if the data is the same. min_minus_1 is OK because @@ -1006,7 +1046,7 @@ DiscreteSpace::mark(DiscreteSpace::range_type const& range return *this; // request is covered by existing span with the same data } else { // request span is covered by existing span. - x = new Node{range, payload}; // + x = _fa.make(range, payload); // n->assign_min(max_plus_1); // clip existing. this->insert_before(n, x); return *this; @@ -1187,7 +1227,7 @@ DiscreteSpace::fill(DiscreteSpace::range_type const& range } } else { // no carry node. if (max < n->_min) { // entirely before next span. - this->insert_before(n, new Node(min, max, payload)); + this->insert_before(n, _fa.make(min, max, payload)); return *this; } else { if (min < n->_min) { // leading section, need node. diff --git a/code/include/swoc/swoc_ip.h b/code/include/swoc/swoc_ip.h index 203a32b..43c385b 100644 --- a/code/include/swoc/swoc_ip.h +++ b/code/include/swoc/swoc_ip.h @@ -1034,6 +1034,20 @@ class IPRange { */ IPRange(string_view const& text); + /// Equality + bool operator == (self_type const& that) const { + if (_family != that._family) { + return false; + } + if (this->is_ip4()) { + return _range._ip4 == that._range._ip4; + } + if (this->is_ip6()) { + return _range._ip6 == that._range._ip6; + } + return true; + } + /// @return @c true if this is an IPv4 range, @c false if not. bool is_ip4() const { return AF_INET == _family; } @@ -1400,6 +1414,7 @@ template class IPSpace { public: using payload_t = PAYLOAD; ///< Export payload type. + using value_type = std::tuple; /// Construct an empty space. IPSpace() = default; @@ -1424,6 +1439,13 @@ template class IPSpace { */ self_type& fill(IPRange const& range, PAYLOAD const& payload); + /** Erase addresses in @a range. + * + * @param range Address range. + * @return @a this + */ + self_type& erase(IPRange const& range); + /** Blend @a color in to the @a range. * * @tparam F Blending functor type (deduced). @@ -2676,6 +2698,16 @@ auto IPSpace::fill(IPRange const& range, PAYLOAD const& payload) -> sel return *this; } +template +auto IPSpace::erase(IPRange const& range) -> self_type& { + if (range.is(AF_INET)) { + _ip4.erase(range.ip4()); + } else if (range.is(AF_INET6)) { + _ip6.erase(range.ip6()); + } + return *this; +} + template template auto IPSpace::blend(IPRange const& range, U const& color, F&& blender) -> self_type& { diff --git a/example/ex_netcompact.cc b/example/ex_netcompact.cc index a902c37..a5002c2 100644 --- a/example/ex_netcompact.cc +++ b/example/ex_netcompact.cc @@ -36,7 +36,7 @@ using swoc::IPRange; /// Type for temporary buffer writer output. using W = swoc::LocalBufferWriter<512>; -/// IPSpace for mapping address to @c Payload +/// IPSpace for mapping address. Treating it as a set so use a no-data payload. using Space = swoc::IPSpace; /// Process the @a content of a file in to @a space. @@ -85,8 +85,8 @@ int main(int argc, char *argv[]) { // Dump the resulting space. unsigned n_nets = 0; - for ( auto && [ r, p] : space) { - for ( auto && net : r.networks()) { + for ( auto && [range, payload] : space ) { + for ( auto && net : range.networks() ) { ++n_nets; std::cout << W().print("{}\n", net); } @@ -94,7 +94,7 @@ int main(int argc, char *argv[]) { auto delta = std::chrono::system_clock::now() - t0; - std::cout << W().print("{} ranges in, {} ranges condensed, {} networks out in {} ms\n" + std::cerr << W().print("{} ranges in, {} ranges condensed, {} networks out in {} ms\n" , n_ranges, space.count(), n_nets , std::chrono::duration_cast(delta).count()); diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc index ff8a5c3..d703d29 100644 --- a/unit_tests/test_ip.cc +++ b/unit_tests/test_ip.cc @@ -538,8 +538,8 @@ TEST_CASE("IP ranges and networks", "[libswoc][ip][net][range]") { } TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { - using int_space = swoc::IPSpace; - int_space space; + using uint_space = swoc::IPSpace; + uint_space space; REQUIRE(space.count() == 0); @@ -627,12 +627,43 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { } CHECK(7 == space.count()); + // Make sure all of these addresses yield the same result. CHECK(space.end() != space.find(IP4Addr{"100.0.4.16"})); CHECK(space.end() != space.find(IPAddr{"100.0.4.16"})); CHECK(space.end() != space.find(IPAddr{IPEndpoint{"100.0.4.16:80"}})); + // same for negative result + CHECK(space.end() == space.find(IP4Addr{"10.0.4.16"})); + CHECK(space.end() == space.find(IPAddr{"10.0.4.16"})); + CHECK(space.end() == space.find(IPAddr{IPEndpoint{"10.0.4.16:80"}})); - auto b2 = [] (unsigned &lhs, unsigned const& rhs) { lhs = rhs; return true; }; + std::array, 3> r_clear = { + { + {"2.2.2.2-2.2.2.40", 0} + , {"2.2.2.50-2.2.2.60", 1} + , {"2.2.2.70-2.2.2.100", 2} + }}; + space.clear(); + for (auto &&[text, value] : r_clear) { + IPRange range{text}; + space.mark(IPRange{text}, value); + } + CHECK(space.count() == 3); + space.erase(IPRange{"2.2.2.35-2.2.2.75"}); + CHECK(space.count() == 2); + { + spot = space.begin(); + auto [ r0, p0 ] = *spot; + auto [ r2, p2 ] = *++spot; + CHECK(r0 == IPRange{"2.2.2.2-2.2.2.34"}); + CHECK(p0 == 0); + CHECK(r2 == IPRange{"2.2.2.76-2.2.2.100"}); + CHECK(p2 == 2); + } + // This is about testing repeated colorings of the same addresses, which happens quite a + // bit in normal network datasets. In fact, the test dataset is based on such a dataset + // and its use. + auto b2 = [] (unsigned &lhs, unsigned const& rhs) { lhs = rhs; return true; }; std::array, 31> r2 = { { {"2001:4998:58:400::1/128", 1} // 1 @@ -669,6 +700,7 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { }}; space.clear(); + // Start with basic blending. for (auto &&[text, value] : r2) { IPRange range{text}; space.blend(IPRange{text}, value, b2); @@ -676,6 +708,7 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { REQUIRE(space.end() != space.find(range.max())); } CHECK(6 == space.count()); + // Do the exact same networks again, should not change the range count. for (auto &&[text, value] : r2) { IPRange range{text}; space.blend(IPRange{text}, value, b2); @@ -683,12 +716,13 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { REQUIRE(space.end() != space.find(range.max())); } CHECK(6 == space.count()); + // Verify that earlier ranges are still valid after the double blend. for (auto &&[text, value] : r2) { IPRange range{text}; REQUIRE(space.end() != space.find(range.min())); REQUIRE(space.end() != space.find(range.max())); } - // Drop a non-intersecting range between existing ranges 1 and 2, make sure both sides coalesce. + // Color the non-intersecting range between ranges 1 and 2, verify coalesce. space.blend(IPRange{"2001:4998:58:400::C/126"_tv}, 1, b2); CHECK(5 == space.count()); // Verify all the data is in the ranges.