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

bneradt pushed a commit to branch dev-1-0-12
in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git

commit 80b73b835997ef7b63a980bc5b961d200621be13
Author: Alan M. Carroll <[email protected]>
AuthorDate: Mon Feb 17 06:16:42 2020 -0600

    Adding IPSpace property map example.
---
 swoc++/include/swoc/DiscreteRange.h |   6 +-
 swoc++/include/swoc/swoc_ip.h       |  25 +++
 swoc++/src/bw_ip_format.cc          |   4 +-
 swoc++/src/swoc_ip.cc               |   9 +
 unit_tests/CMakeLists.txt           |   1 +
 unit_tests/ex_ipspace_properties.cc | 332 ++++++++++++++++++++++++++++++++++++
 unit_tests/test_ip.cc               |  57 +------
 7 files changed, 375 insertions(+), 59 deletions(-)

diff --git a/swoc++/include/swoc/DiscreteRange.h 
b/swoc++/include/swoc/DiscreteRange.h
index f98f7db..9ea907f 100644
--- a/swoc++/include/swoc/DiscreteRange.h
+++ b/swoc++/include/swoc/DiscreteRange.h
@@ -250,7 +250,7 @@ public:
   bool is_singleton() const;
 
   //! Check if the interval is empty.
-  bool is_empty() const;
+  bool empty() const;
 
   /** Test for empty, operator form.
       @return @c true if the interval is empty, @c false otherwise.
@@ -364,7 +364,7 @@ DiscreteRange<T>::is_singleton() const {
 
 template <typename T>
 bool
-DiscreteRange<T>::is_empty() const {
+DiscreteRange<T>::empty() const {
   return _min > _max;
 }
 
@@ -1277,7 +1277,7 @@ DiscreteSpace<METRIC, 
PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c
         }
       } else if (pred_plain_colored_p) { // can pull @a pred right to cover.
         pred->assign_max(remaining.max());
-      } else if (! remaining.is_empty()) { // Must add new range.
+      } else if (!remaining.empty()) { // Must add new range.
         this->insert_before(n, _fa.make(remaining.min(), remaining.max(), 
plain_color));
       }
       return *this;
diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h
index ebd365a..b327c49 100644
--- a/swoc++/include/swoc/swoc_ip.h
+++ b/swoc++/include/swoc/swoc_ip.h
@@ -642,6 +642,8 @@ public:
   /// @return The maximum address in the range.
   IPAddr max() const;
 
+  bool empty() const;
+
   operator IP4Range & () { return _range._ip4; }
   operator IP6Range & () { return _range._ip6; }
   operator IP4Range const & () const { return _range._ip4; }
@@ -797,6 +799,29 @@ public:
     return _ip4.find(addr);
   }
 
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return The payload if any, @c nullptr if the address is not in the space.
+   */
+  PAYLOAD *find(IP6Addr const &addr) {
+    return _ip6.find(addr);
+  }
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return The payload if any, @c nullptr if the address is not in the space.
+   */
+  PAYLOAD *find(IPAddr const &addr) {
+    if (addr.is_ip4()) {
+      return _ip4.find(IP4Addr{addr});
+    } else if (addr.is_ip6()) {
+      return _ip6.find(IP6Addr{addr});
+    }
+    return nullptr;
+  }
+
   /// @return The number of distinct ranges.
   size_t count() const { return _ip4.count() + _ip6.count(); }
 
diff --git a/swoc++/src/bw_ip_format.cc b/swoc++/src/bw_ip_format.cc
index a87969c..e0e8b58 100644
--- a/swoc++/src/bw_ip_format.cc
+++ b/swoc++/src/bw_ip_format.cc
@@ -302,14 +302,14 @@ bwformat(BufferWriter &w, Spec const &spec, IPAddr const 
&addr)
 
 BufferWriter &
 bwformat(BufferWriter & w, Spec const& spec, IP4Range const& range) {
-  return range.is_empty()
+  return range.empty()
   ? w.write("*-*"_tv)
   : w.print("{}-{}", range.min(), range.max());
 }
 
 BufferWriter &
 bwformat(BufferWriter & w, Spec const& spec, IP6Range const& range) {
-  return range.is_empty()
+  return range.empty()
          ? w.write("*-*"_tv)
          : w.print("{}-{}", range.min(), range.max());
 }
diff --git a/swoc++/src/swoc_ip.cc b/swoc++/src/swoc_ip.cc
index 1c1675e..391988e 100644
--- a/swoc++/src/swoc_ip.cc
+++ b/swoc++/src/swoc_ip.cc
@@ -659,4 +659,13 @@ IPAddr IPRange::max() const {
   return {};
 }
 
+bool IPRange::empty() const {
+  switch (_family) {
+    case AF_INET: return _range._ip4.empty();
+    case AF_INET6: return _range._ip6.empty();
+    default: break;
+  }
+  return true;
+}
+
 } // namespace swoc
diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt
index b9f550d..5bddd95 100644
--- a/unit_tests/CMakeLists.txt
+++ b/unit_tests/CMakeLists.txt
@@ -21,6 +21,7 @@ add_executable(test_libswoc
 
     ex_bw_format.cc
     ex_IntrusiveDList.cc
+    ex_ipspace_properties.cc
     ex_MemArena.cc
     ex_TextView.cc
     )
diff --git a/unit_tests/ex_ipspace_properties.cc 
b/unit_tests/ex_ipspace_properties.cc
new file mode 100644
index 0000000..5a395da
--- /dev/null
+++ b/unit_tests/ex_ipspace_properties.cc
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014 Network Geographics
+
+/** @file
+
+    Example use of IPSpace for property mapping.
+*/
+
+#include "catch.hpp"
+
+#include <memory>
+#include <limits>
+
+#include <swoc/TextView.h>
+#include <swoc/swoc_ip.h>
+#include <swoc/bwf_ip.h>
+#include <swoc/swoc_file.h>
+
+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;
+using swoc::IPSpace;
+
+using swoc::MemSpan;
+using swoc::MemArena;
+
+using W = swoc::LocalBufferWriter<256>;
+
+// Property maps for IPSpace.
+
+/// A @c Property is a collection of names which are values of the property.
+class Property {
+  using self_type = Property;
+public:
+  /// A handle to an instance.
+  using Handle = std::unique_ptr<self_type>;
+
+  /** Construct an instance.
+   *
+   * @param idx THe property index in a row.
+   */
+  Property(TextView  const& name) : _name(name) {};
+
+  virtual ~Property() = default;
+
+  virtual size_t size() const = 0;
+
+  unsigned idx() const { return _idx; }
+
+  virtual bool needs_localized_token() const { return false; }
+
+  self_type & assign_idx(unsigned idx) { _idx = idx; return *this; }
+  /// Assign the @a offset in bytes for this property.
+  self_type & assign_offset(size_t offset) { _offset = offset; return *this; }
+  size_t offset() const { return _offset; }
+
+  virtual bool parse(TextView token, MemSpan<std::byte> span) = 0;
+
+protected:
+  TextView _name;
+  unsigned _idx = std::numeric_limits<unsigned>::max(); ///< Column index.
+  size_t _offset = std::numeric_limits<size_t>::max(); ///< Offset into a row.
+};
+
+// ---
+
+class Table {
+  using self_type = Table;
+public:
+  static constexpr char SEP = ',';
+
+  Table() = default;
+
+  /** Add a column to the table.
+   *
+   * @param col Column descriptor.
+   * @return @a this
+   */
+  self_type & add_column(Property::Handle && col);
+
+  /// A row for the table.
+  class Row {
+    using self_type = Row;
+  public:
+    Row(MemSpan<std::byte> span) : _data(span) {}
+    MemSpan<std::byte> span_for(Property const& prop) const {
+      return MemSpan<std::byte>{_data}.remove_prefix(prop.offset());
+    }
+  protected:
+    MemSpan<std::byte> _data;
+  };
+
+  bool parse(TextView src);
+
+  Row* find(IPAddr const& addr);
+
+  size_t size() const { return _space.count(); }
+
+  /// Return the property for the column @a idx.
+  Property * column(unsigned idx) { return _columns[idx].get(); }
+
+protected:
+  size_t _size = 0;
+  std::vector<Property::Handle> _columns;
+
+  using space = IPSpace<Row>;
+  space _space;
+
+  MemArena _arena;
+
+  TextView token(TextView & src);
+
+  TextView localize(TextView const& src);
+};
+
+auto Table::add_column(Property::Handle &&col) -> self_type & {
+  col->assign_offset(_size);
+  col->assign_idx(_columns.size());
+  _size += col->size();
+  _columns.emplace_back(std::move(col));
+  return *this;
+}
+
+TextView Table::localize(TextView const&src) {
+  auto span = _arena.alloc(src.size()).rebind<char>();
+  memcpy(span, src);
+  return span.view();
+}
+
+TextView Table::token(TextView & src) {
+  TextView::size_type idx = 0;
+  // Characters of interest in a null terminated string.
+  char sep_list[3] = {'"', SEP, 0};
+  bool in_quote_p  = false;
+  while (idx < src.size()) {
+    // Next character of interest.
+    idx = src.find_first_of(sep_list, idx);
+    if (TextView::npos == idx) {
+      // no more, consume all of @a src.
+      break;
+    } else if ('"' == src[idx]) {
+      // quote, skip it and flip the quote state.
+      in_quote_p = !in_quote_p;
+      ++idx;
+    } else if (SEP == src[idx]) { // separator.
+      if (in_quote_p) {
+        // quoted separator, skip and continue.
+        ++idx;
+      } else {
+        // found token, finish up.
+        break;
+      }
+    }
+  }
+
+  // clip the token from @a src and trim whitespace, quotes
+  auto zret = src.take_prefix(idx).trim_if(&isspace).trim('"');
+  return zret;
+}
+
+bool Table::parse(TextView src) {
+  unsigned line_no = 0;
+  while (src) {
+    auto line = src.take_prefix_at('\n');
+    ++line_no;
+    auto range_token = line.take_prefix_at(',');
+    IPRange range{range_token};
+    if (range.empty()) {
+      std::cout << W().print("{} is not a valid range specification.", 
range_token);
+      continue; // This is an error, real code should report it.
+    }
+    MemSpan<std::byte> span = _arena.alloc(_size).rebind<std::byte>();
+    Row row{span};
+    for ( auto const& col : _columns) {
+      auto token = this->token(line);
+      if (col->needs_localized_token()) {
+        token = this->localize(token);
+      }
+      if (! col->parse(token, MemSpan<std::byte>{span.data(), col->size()})) {
+        std::cout << W().print("Value \"{}\" at index {} on line {} is 
invalid.", token, col->idx(), line_no);
+      }
+      span.remove_prefix(col->size());
+    }
+    _space.mark(range, std::move(row));
+  }
+  return true;
+}
+
+auto Table::find(IPAddr const &addr) -> Row * {
+  return _space.find(addr);
+}
+
+bool operator == (Table::Row const&, Table::Row const&) { return false; }
+
+// ---
+
+class FlagProperty : public Property {
+  using self_type = FlagProperty;
+  using super_type = Property;
+public:
+  static constexpr size_t SIZE = sizeof(bool);
+protected:
+  size_t size() const override { return SIZE; }
+  bool parse(TextView token, MemSpan<std::byte> span) override;
+};
+
+class FlagGroupProperty : public Property {
+  using self_type = FlagGroupProperty;
+  using super_type = Property;
+public:
+  static constexpr size_t SIZE = sizeof(uint8_t);
+  FlagGroupProperty(TextView const& name, std::initializer_list<TextView> 
tags);
+
+  bool is_set(unsigned idx, Table::Row const& row) const;
+protected:
+  size_t size() const override { return SIZE; }
+  bool parse(TextView token, MemSpan<std::byte> span) override;
+  std::vector<TextView> _tags;
+};
+
+class TagProperty : public Property {
+  using self_type = TagProperty;
+  using super_type = Property;
+public: // owner
+  static constexpr size_t SIZE = sizeof(uint8_t);
+  using super_type::super_type;
+protected:
+  std::vector<TextView> _tags;
+
+  size_t size() const override { return SIZE; }
+  bool parse(TextView token, MemSpan<std::byte> span) override;
+};
+
+class StringProperty : public Property {
+  using self_type = StringProperty;
+  using super_type = Property;
+public:
+  static constexpr size_t SIZE = sizeof(TextView);
+  using super_type::super_type;
+
+protected:
+  size_t size() const override { return SIZE; }
+  bool parse(TextView token, MemSpan<std::byte> span) override;
+  bool needs_localized_token() const { return true; }
+};
+
+// ---
+bool StringProperty::parse(TextView token, MemSpan<std::byte> span) {
+  memcpy(span.data(), &token, sizeof(token));
+  return true;
+}
+
+FlagGroupProperty::FlagGroupProperty(TextView const& name, 
std::initializer_list<TextView> tags)
+    : super_type(name)
+{
+  _tags.reserve(tags.size());
+  for ( auto const& tag : tags ) {
+    _tags.emplace_back(tag);
+  }
+}
+
+bool FlagGroupProperty::parse(TextView token, MemSpan<std::byte> span) {
+  if ("-"_tv == token) { return true; } // marker for no flags.
+  memset(span, 0);
+  while (token) {
+    auto tag = token.take_prefix_at(';');
+    unsigned j = 0;
+    for ( auto const& key : _tags ) {
+      if (0 == strcasecmp(key, tag)) {
+        span[j/8] |= (std::byte{1} << (j % 8));
+        break;
+      }
+      ++j;
+    }
+    if (j > _tags.size()) {
+      std::cout << W().print("Tag \"{}\" is not recognized.", tag);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool FlagGroupProperty::is_set(unsigned flag_idx, Table::Row const& row) const 
{
+  auto sp = row.span_for(*this);
+  return std::byte{0} != ((sp[flag_idx/8] >> (flag_idx%8)) & std::byte{1});
+}
+
+bool TagProperty::parse(TextView token, MemSpan<std::byte> span) {
+  // Already got one?
+  auto spot = std::find_if(_tags.begin(), _tags.end(), [&](TextView const& 
tag) { return 0 == strcasecmp(token, tag); });
+  if (spot == _tags.end()) { // nope, add it to the list.
+    _tags.push_back(token);
+    spot = std::prev(_tags.end());
+  }
+  span.rebind<uint8_t>()[0] = spot - _tags.begin();
+  return true;
+}
+
+// ---
+
+TEST_CASE("IPSpace properties", "[libswoc][ip][ex][properties]") {
+  Table table;
+  auto flag_names = { "prod"_tv, "dmz"_tv, "internal"_tv};
+  table.add_column(std::make_unique<TagProperty>("owner"));
+  table.add_column(std::make_unique<TagProperty>("colo"));
+  table.add_column(std::make_unique<FlagGroupProperty>("flags"_tv, 
flag_names));
+  table.add_column(std::make_unique<StringProperty>("Description"));
+
+  TextView src = R"(10.1.1.0/24,asf,cmi,prod;internal,"ASF core net"
+192.168.28.0/25,asf,ind,prod,"Indy Net"
+192.168.28.128/25,asf,abq,dmz;internal,"Albuquerque zone"
+)";
+
+  REQUIRE(true == table.parse(src));
+  REQUIRE(3 == table.size());
+  auto row = table.find(IPAddr{"10.1.1.56"});
+  REQUIRE(nullptr != row);
+  CHECK(true == static_cast<FlagGroupProperty*>(table.column(2))->is_set(0, 
*row));
+  CHECK(false == static_cast<FlagGroupProperty*>(table.column(2))->is_set(1, 
*row));
+  CHECK(true == static_cast<FlagGroupProperty*>(table.column(2))->is_set(2, 
*row));
+};
+
diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc
index b210666..582b531 100644
--- a/unit_tests/test_ip.cc
+++ b/unit_tests/test_ip.cc
@@ -263,57 +263,6 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") {
   REQUIRE(w.view() == "   0:   0:   0:   0:   0:   0:   0:   1");
 }
 
-// Property maps for IPSpace.
-
-/** A @c Property is a collection of names which are values of the property.
- *
- */
-class Property {
-  using self_type = Property;
-public:
-  Property(unsigned idx) : _idx(idx) {}
-
-  unsigned operator[](std::string_view const&);
-
-protected:
-  unsigned _idx;
-  std::vector<std::string> _names;
-};
-
-unsigned Property::operator[](std::string_view const&name) {
-  if (auto spot = std::find_if(_names.begin(), _names.end()
-                               , [&](std::string const&prop) { return 
strcasecmp(name, prop); });
-      spot != _names.end()) {
-    return spot - _names.begin();
-  }
-  // Create a value
-  _names.emplace_back(name);
-  return _names.size() - 1;
-}
-
-class PropertyGroup {
-  using self_type = PropertyGroup;
-public:
-  Property *operator[](std::string_view const&name);
-
-protected:
-  using Item = std::tuple<std::string, std::unique_ptr<Property>>;
-  std::vector<Item> _properties;
-};
-
-
-Property *PropertyGroup::operator[](std::string_view const&name) {
-  if (auto spot = std::find_if(_properties.begin(), _properties.end()
-                               , [&](
-            Item const&item) { return strcasecmp(name, std::get<0>(item)); });
-      spot != _properties.end()) {
-    return std::get<1>(*spot).get();
-  }
-  // Create a new property.
-  _properties.emplace_back(name, new Property(_properties.size()));
-  return std::get<1>(_properties.back()).get();
-}
-
 TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") {
   using int_space = swoc::IPSpace<unsigned>;
   int_space space;
@@ -328,16 +277,16 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") {
   REQUIRE(space.count() == 0);
 
   space.mark(IPRange{{IP4Addr("172.16.0.0"), IP4Addr("172.16.0.255")}}, 1);
-  auto result = space.find({"172.16.0.97"});
+  auto result = space.find(IPAddr{"172.16.0.97"});
   REQUIRE(result != nullptr);
   REQUIRE(*result == 1);
 
-  result = space.find({"172.17.0.97"});
+  result = space.find(IPAddr{"172.17.0.97"});
   REQUIRE(result == nullptr);
 
   space.mark(IPRange{"172.16.0.12-172.16.0.25"_tv}, 2);
 
-  result = space.find({"172.16.0.21"});
+  result = space.find(IPAddr{"172.16.0.21"});
   REQUIRE(result != nullptr);
   REQUIRE(*result == 2);
   REQUIRE(space.count() == 3);

Reply via email to