Skip to content

Commit

Permalink
Add IPSpace::erase.
Browse files Browse the repository at this point in the history
  • Loading branch information
SolidWallOfCode committed Apr 22, 2020
1 parent 0092257 commit 86de876
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 14 deletions.
52 changes: 46 additions & 6 deletions code/include/swoc/DiscreteRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ template<typename T> 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.
Expand Down Expand Up @@ -971,6 +981,37 @@ DiscreteSpace<METRIC, PAYLOAD>::insert_after(DiscreteSpace::Node *spot, Discrete
_root = static_cast<Node *>(node->rebalance_after_insert());
}

template<typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD>&
DiscreteSpace<METRIC, PAYLOAD>::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<typename METRIC, typename PAYLOAD>
DiscreteSpace<METRIC, PAYLOAD>&
DiscreteSpace<METRIC, PAYLOAD>::mark(DiscreteSpace::range_type const& range
Expand All @@ -979,16 +1020,15 @@ DiscreteSpace<METRIC, PAYLOAD>::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
Expand All @@ -1006,7 +1046,7 @@ DiscreteSpace<METRIC, PAYLOAD>::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;
Expand Down Expand Up @@ -1187,7 +1227,7 @@ DiscreteSpace<METRIC, PAYLOAD>::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.
Expand Down
32 changes: 32 additions & 0 deletions code/include/swoc/swoc_ip.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -1400,6 +1414,7 @@ template<typename PAYLOAD> class IPSpace {

public:
using payload_t = PAYLOAD; ///< Export payload type.
using value_type = std::tuple<IPRange const, PAYLOAD&>;

/// Construct an empty space.
IPSpace() = default;
Expand All @@ -1424,6 +1439,13 @@ template<typename PAYLOAD> 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).
Expand Down Expand Up @@ -2676,6 +2698,16 @@ auto IPSpace<PAYLOAD>::fill(IPRange const& range, PAYLOAD const& payload) -> sel
return *this;
}

template<typename PAYLOAD>
auto IPSpace<PAYLOAD>::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<typename PAYLOAD>
template<typename F, typename U>
auto IPSpace<PAYLOAD>::blend(IPRange const& range, U const& color, F&& blender) -> self_type& {
Expand Down
8 changes: 4 additions & 4 deletions example/ex_netcompact.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::monostate>;

/// Process the @a content of a file in to @a space.
Expand Down Expand Up @@ -85,16 +85,16 @@ 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);
}
}

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<std::chrono::milliseconds>(delta).count());

Expand Down
42 changes: 38 additions & 4 deletions unit_tests/test_ip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned>;
int_space space;
using uint_space = swoc::IPSpace<unsigned>;
uint_space space;

REQUIRE(space.count() == 0);

Expand Down Expand Up @@ -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<std::tuple<TextView, int>, 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<std::tuple<TextView, unsigned>, 31> r2 = {
{
{"2001:4998:58:400::1/128", 1} // 1
Expand Down Expand Up @@ -669,26 +700,29 @@ 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);
REQUIRE(space.end() != space.find(range.min()));
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);
REQUIRE(space.end() != space.find(range.min()));
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.
Expand Down

0 comments on commit 86de876

Please sign in to comment.