You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bn...@apache.org on 2024/01/30 16:26:38 UTC

(trafficserver-libswoc) branch dev-1-0-9 created (now 6c3a3b8)

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

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


      at 6c3a3b8  Fix version, version update script.

This branch includes the following new commits:

     new 968de36  Lexicon: Change default handlers to make them available for constant instances.
     new cc3e314  Upgrade to 1.0.9.
     new 36b66e5  Update clang format config.
     new d6fbf55  Add FixedArena.
     new e651aec  First successful unit test of IPSpace. (#15)
     new 3f35e4b  Documentation update for IP Networking.
     new eb5adef  Change IPSpace::blend to take an arbitrary type for the blending color.
     new 6c3a3b8  Fix version, version update script.

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(trafficserver-libswoc) 02/08: Upgrade to 1.0.9.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit cc3e3146bdba8f6cb673e8e661317c89eb58803f
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Sat Oct 19 12:22:48 2019 -0500

    Upgrade to 1.0.9.
---
 CMakeLists.txt                     |  1 +
 doc/Doxyfile                       |  2 +-
 doc/conf.py                        |  4 ++--
 swoc++/CMakeLists.txt              |  2 +-
 swoc++/include/swoc/swoc_version.h |  6 +++---
 tools/update-version.sh            | 16 ++++++++++++++++
 6 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b615fdb..d0aecab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,6 @@
 cmake_minimum_required(VERSION 3.12)
 
+project("Solid Wall Of C++ Library")
 set(INSTALL_DIR ${CMAKE_HOME_DIRECTORY})
 
 # Fortunately this has no external dependencies so the set up can be simple.
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 69b1637..155a1e5 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME           = "LibSWOC++"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = "1.0.8"
+PROJECT_NUMBER         = "1.0.9"
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/doc/conf.py b/doc/conf.py
index f5b0c42..72bdcf6 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -80,7 +80,7 @@ project = u'Solid Wall Of C++'
 copyright = u'{}, amc@apache.org'.format(date.today().year)
 
 # The full version, including alpha/beta/rc tags.
-release = "1.0.8"
+release = "1.0.9"
 # The short X.Y version.
 version = '.'.join(release.split('.', 2)[:2])
 
@@ -326,7 +326,7 @@ texinfo_documents = [
 epub_title = u'Solid Wall of C++'
 epub_author = u'amc@apache.org'
 epub_publisher = u'amc@apache.org'
-epub_copyright = u'2018, amc@apache.org'
+epub_copyright = u'2019, amc@apache.org'
 
 # The language of the text. It defaults to the language option
 # or en if the language is not set.
diff --git a/swoc++/CMakeLists.txt b/swoc++/CMakeLists.txt
index 898279e..bbf358e 100644
--- a/swoc++/CMakeLists.txt
+++ b/swoc++/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.12)
 
 project(lib-swoc++ CXX)
-set(LIBSWOC_VERSION "1.0.8")
+set(LIBSWOC_VERSION "1.0.9")
 set(CMAKE_CXX_STANDARD 17)
 include(GNUInstallDirs)
 
diff --git a/swoc++/include/swoc/swoc_version.h b/swoc++/include/swoc/swoc_version.h
index 4eb8d6c..36dea4b 100644
--- a/swoc++/include/swoc/swoc_version.h
+++ b/swoc++/include/swoc/swoc_version.h
@@ -37,8 +37,8 @@
 
 namespace swoc
 {
-static constexpr unsigned MAJOR_VERION  = 1;
-static constexpr unsigned MINOR_VERSION = 0;
-static constexpr unsigned POINT_VERSION = 8;
+static constexpr unsigned MAJOR_VERSION = 1
+static constexpr unsigned MINOR_VERSION = 0
+static constexpr unsigned POINT_VERSION = 9
 
 } // namespace swoc
diff --git a/tools/update-version.sh b/tools/update-version.sh
new file mode 100644
index 0000000..2413f8d
--- /dev/null
+++ b/tools/update-version.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+if [ -z "$3" ] ; then
+  echo "Usage: $0 major minor point"
+  exit 1
+fi
+
+# Header
+sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(MAJOR_VERSION *= *\).*\$/\\1$1/"
+sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(MINOR_VERSION *= *\).*\$/\\1$2/"
+sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(POINT_VERSION *= *\).*\$/\\1$3/"
+
+sed -i doc/conf.py --expr "s/release = .*\$/release = \"$1.$2.$3\"/"
+sed -i doc/Doxyfile --expr "s/\(PROJECT_NUMBER *= *\).*\$/\\1\"$1.$2.$3\"/"
+
+sed -i swoc++/CMakeLists.txt --expr "s/\(LIBSWOC_VERSION *\)\"[^\"]*\"/\\1\"$1.$2.$3\"/"


(trafficserver-libswoc) 03/08: Update clang format config.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 36b66e5cd504e009cfb5e80d5670f9b50a09329e
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Sun Nov 10 15:14:04 2019 -0600

    Update clang format config.
---
 .clang-format | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/.clang-format b/.clang-format
index 7c2364a..7f2631c 100644
--- a/.clang-format
+++ b/.clang-format
@@ -13,8 +13,8 @@ AllowShortCaseLabelsOnASingleLine: false
 AllowShortFunctionsOnASingleLine: Inline
 AllowShortIfStatementsOnASingleLine: false
 AllowShortLoopsOnASingleLine: false
-AlwaysBreakAfterDefinitionReturnType: All
-AlwaysBreakAfterReturnType: AllDefinitions
+#AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
 AlwaysBreakBeforeMultilineStrings: false
 AlwaysBreakTemplateDeclarations: false
 BinPackArguments: true
@@ -23,7 +23,7 @@ BraceWrapping:
   AfterClass:      true
   AfterControlStatement: true
   AfterEnum:       true
-  AfterFunction:   true
+  AfterFunction:   false
   AfterNamespace:  true
   AfterObjCDeclaration: false
   AfterStruct:     true
@@ -92,10 +92,10 @@ PenaltyBreakString: 1000
 PenaltyExcessCharacter: 1000000
 PenaltyReturnTypeOnItsOwnLine: 200
 PointerAlignment: Right
-RawStringFormats: 
-  - Delimiter:       pb
-    Language:        TextProto
-    BasedOnStyle:    Mozilla
+#RawStringFormats: 
+#  - Delimiter:       'pb'
+#    Language:        TextProto
+#    BasedOnStyle:    Mozilla
 ReflowComments:  true
 SortIncludes:    false
 SortUsingDeclarations: false


(trafficserver-libswoc) 05/08: First successful unit test of IPSpace. (#15)

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e651aec2d9ee52cab4f68dad7422bdc1ff1eff92
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Mon Dec 2 09:36:00 2019 -0600

    First successful unit test of IPSpace. (#15)
---
 .clang-format                          |   26 +-
 CMakeLists.txt                         |    3 +-
 swoc++/CMakeLists.txt                  |    5 +-
 swoc++/include/swoc/ArenaWriter.h      |    3 +-
 swoc++/include/swoc/BufferWriter.h     |  117 ++-
 swoc++/include/swoc/DiscreteRange.h    | 1305 ++++++++++++++++++++++++++++++++
 swoc++/include/swoc/Errata.h           |  203 ++---
 swoc++/include/swoc/IntrusiveDList.h   |  278 +++----
 swoc++/include/swoc/IntrusiveHashMap.h |  191 +++--
 swoc++/include/swoc/Lexicon.h          |  168 ++--
 swoc++/include/swoc/MemArena.h         |   89 +--
 swoc++/include/swoc/MemSpan.h          |  232 ++----
 swoc++/include/swoc/RBTree.h           |   58 +-
 swoc++/include/swoc/Scalar.h           |  319 +++-----
 swoc++/include/swoc/TextView.h         |  344 ++++-----
 swoc++/include/swoc/bwf_ex.h           |   43 +-
 swoc++/include/swoc/bwf_ip.h           |   11 +-
 swoc++/include/swoc/bwf_std.h          |    3 +-
 swoc++/include/swoc/swoc_file.h        |   42 +-
 swoc++/include/swoc/swoc_ip.h          |  867 +++++++++++++--------
 swoc++/include/swoc/swoc_meta.h        |    6 +-
 swoc++/include/swoc/swoc_version.h     |    4 +-
 swoc++/src/RBTree.cc                   |    2 +-
 swoc++/src/bw_ip_format.cc             |   18 +-
 swoc++/src/swoc_ip.cc                  |  528 +++++++------
 unit_tests/ex_TextView.cc              |   65 ++
 unit_tests/examples/resolver.txt       |   17 +
 unit_tests/test_ip.cc                  |  488 +++++++++---
 28 files changed, 3464 insertions(+), 1971 deletions(-)

diff --git a/.clang-format b/.clang-format
index 7f2631c..0c27808 100644
--- a/.clang-format
+++ b/.clang-format
@@ -19,15 +19,15 @@ AlwaysBreakBeforeMultilineStrings: false
 AlwaysBreakTemplateDeclarations: false
 BinPackArguments: true
 BinPackParameters: true
-BraceWrapping:   
-  AfterClass:      true
-  AfterControlStatement: true
-  AfterEnum:       true
+BraceWrapping:
+  AfterClass:      false
+  AfterControlStatement: false
+  AfterEnum:       false
   AfterFunction:   false
   AfterNamespace:  true
   AfterObjCDeclaration: false
-  AfterStruct:     true
-  AfterUnion:      true
+  AfterStruct:     false
+  AfterUnion:      false
   AfterExternBlock: true
   BeforeCatch:     false
   BeforeElse:      false
@@ -36,7 +36,7 @@ BraceWrapping:
   SplitEmptyRecord: false
   SplitEmptyNamespace: false
 BreakBeforeBinaryOperators: None
-BreakBeforeBraces: Linux
+BreakBeforeBraces: Custom
 BreakBeforeInheritanceComma: false
 BreakBeforeTernaryOperators: false
 BreakConstructorInitializersBeforeComma: false
@@ -54,7 +54,7 @@ DerivePointerAlignment: false
 DisableFormat:   false
 ExperimentalAutoDetectBinPacking: false
 FixNamespaceComments: true
-ForEachMacros:   
+ForEachMacros:
   - forv_Vec
   - for_Vec
   - forp_Vec
@@ -62,7 +62,7 @@ ForEachMacros:
   - Q_FOREACH
   - BOOST_FOREACH
 IncludeBlocks:   Preserve
-IncludeCategories: 
+IncludeCategories:
   - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
     Priority:        2
   - Regex:           '^(<|"(gtest|isl|json)/)'
@@ -92,10 +92,10 @@ PenaltyBreakString: 1000
 PenaltyExcessCharacter: 1000000
 PenaltyReturnTypeOnItsOwnLine: 200
 PointerAlignment: Right
-#RawStringFormats: 
-#  - Delimiter:       'pb'
-#    Language:        TextProto
-#    BasedOnStyle:    Mozilla
+RawStringFormats:
+  - Delimiters:      [ pb ]
+    Language:        TextProto
+    BasedOnStyle:    Mozilla
 ReflowComments:  true
 SortIncludes:    false
 SortUsingDeclarations: false
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d0aecab..7c6e0c4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,4 +16,5 @@ get_target_property(_TMP test_libswoc CLANG_FORMAT_DIRS)
 list(APPEND _CLANG_DIRS ${_TMP})
 list(JOIN _CLANG_DIRS " " _CLANG_DIRS)
 
-add_custom_target(clang-format COMMAND ${CMAKE_HOME_DIRECTORY}/tools/clang-format.sh ${_CLANG_DIRS})
+#add_custom_target(clang-format COMMAND ${CMAKE_HOME_DIRECTORY}/tools/clang-format.sh ${_CLANG_DIRS})
+#add_custom_target(clang-format COMMAND clang-format ${_CLANG_DIRS})
diff --git a/swoc++/CMakeLists.txt b/swoc++/CMakeLists.txt
index bbf358e..81a2c9f 100644
--- a/swoc++/CMakeLists.txt
+++ b/swoc++/CMakeLists.txt
@@ -10,8 +10,10 @@ set(HEADER_FILES
     include/swoc/ArenaWriter.h
     include/swoc/BufferWriter.h
     include/swoc/bwf_base.h
-    include/swoc/bwf_std.h
     include/swoc/bwf_ex.h
+    include/swoc/bwf_ip.h
+    include/swoc/bwf_std.h
+    include/swoc/DiscreteRange.h
     include/swoc/Errata.h
     include/swoc/IntrusiveDList.h
     include/swoc/IntrusiveHashMap.h
@@ -23,7 +25,6 @@ set(HEADER_FILES
     include/swoc/TextView.h
     include/swoc/swoc_file.h
     include/swoc/swoc_meta.h
-    include/swoc/bwf_ip.h
     )
 
 # These are external but required.
diff --git a/swoc++/include/swoc/ArenaWriter.h b/swoc++/include/swoc/ArenaWriter.h
index 8f56e26..2c41150 100644
--- a/swoc++/include/swoc/ArenaWriter.h
+++ b/swoc++/include/swoc/ArenaWriter.h
@@ -19,8 +19,7 @@ namespace swoc
  * the output without overflow. Because it uses the remnant, if there is an error or resizing,
  * no arena memory will be lost.
  */
-class ArenaWriter : public FixedBufferWriter
-{
+class ArenaWriter : public FixedBufferWriter {
   using self_type  = ArenaWriter;       ///< Self reference type.
   using super_type = FixedBufferWriter; ///< Parent type.
 public:
diff --git a/swoc++/include/swoc/BufferWriter.h b/swoc++/include/swoc/BufferWriter.h
index 9388d76..3a61884 100644
--- a/swoc++/include/swoc/BufferWriter.h
+++ b/swoc++/include/swoc/BufferWriter.h
@@ -51,8 +51,7 @@ namespace bwf
  *
  * @note This is a protocol class, concrete subclasses implement the functionality.
  */
-class BufferWriter
-{
+class BufferWriter {
 public:
   /** Write @a c to the buffer.
    *
@@ -295,8 +294,7 @@ public:
 /** A concrete @c BufferWriter class for a fixed buffer.
  *
  */
-class FixedBufferWriter : public BufferWriter
-{
+class FixedBufferWriter : public BufferWriter {
   using super_type = BufferWriter;
   using self_type  = FixedBufferWriter;
 
@@ -427,8 +425,7 @@ protected:
  * create_note(LocalBufferWriter<256>().print("Note {}", idx).view());
  * @endcode
  */
-template <size_t N> class LocalBufferWriter : public FixedBufferWriter
-{
+template <size_t N> class LocalBufferWriter : public FixedBufferWriter {
   using self_type  = LocalBufferWriter;
   using super_type = FixedBufferWriter;
 
@@ -447,60 +444,53 @@ protected:
 inline BufferWriter::~BufferWriter() {}
 
 inline BufferWriter &
-BufferWriter::write(const void *data, size_t length)
-{
+BufferWriter::write(const void *data, size_t length) {
   const char *d = static_cast<const char *>(data);
 
-  while (length--) {
+  while (length--)
+  {
     this->write(*(d++));
   }
   return *this;
 }
 
 inline BufferWriter &
-BufferWriter::write(const std::string_view &sv)
-{
+BufferWriter::write(const std::string_view &sv) {
   return this->write(sv.data(), sv.size());
 }
 
 inline char *
-BufferWriter::aux_data()
-{
+BufferWriter::aux_data() {
   return nullptr;
 }
 
 inline size_t
-BufferWriter::size() const
-{
+BufferWriter::size() const {
   return std::min(this->extent(), this->capacity());
 }
 
 inline size_t
-BufferWriter::remaining() const
-{
+BufferWriter::remaining() const {
   return this->capacity() - this->size();
 }
 
 // --- FixedBufferWriter ---
-inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buffer(buffer), _capacity(capacity)
-{
-  if (_capacity != 0 && buffer == nullptr) {
+inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buffer(buffer), _capacity(capacity) {
+  if (_capacity != 0 && buffer == nullptr)
+  {
     throw(std::invalid_argument{"FixedBufferWriter created with null buffer and non-zero size."});
   };
 }
 
 inline FixedBufferWriter::FixedBufferWriter(MemSpan<void> const &span)
-  : _buffer{static_cast<char *>(span.data())}, _capacity{span.size()}
-{
-}
+  : _buffer{static_cast<char *>(span.data())}, _capacity{span.size()} {}
 
 inline FixedBufferWriter::FixedBufferWriter(MemSpan<char> const &span) : _buffer{span.begin()}, _capacity{span.size()} {}
 
 inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buffer(nullptr), _capacity(0) {}
 
 inline FixedBufferWriter::self_type &
-FixedBufferWriter::detach()
-{
+FixedBufferWriter::detach() {
   const_cast<char *&>(_buffer) = nullptr;
   _capacity                    = 0;
   _attempted                   = 0;
@@ -508,14 +498,12 @@ FixedBufferWriter::detach()
 }
 
 inline FixedBufferWriter::FixedBufferWriter(FixedBufferWriter &&that)
-  : _buffer(that._buffer), _capacity(that._capacity), _attempted(that._attempted)
-{
+  : _buffer(that._buffer), _capacity(that._capacity), _attempted(that._attempted) {
   that.detach();
 }
 
 inline FixedBufferWriter::self_type &
-FixedBufferWriter::assign(MemSpan<char> const &span)
-{
+FixedBufferWriter::assign(MemSpan<char> const &span) {
   const_cast<char *&>(_buffer) = span.data();
   _capacity                    = span.size();
   _attempted                   = 0;
@@ -523,8 +511,7 @@ FixedBufferWriter::assign(MemSpan<char> const &span)
 }
 
 inline FixedBufferWriter &
-FixedBufferWriter::operator=(FixedBufferWriter &&that)
-{
+FixedBufferWriter::operator=(FixedBufferWriter &&that) {
   const_cast<char *&>(_buffer) = that._buffer;
   _capacity                    = that._capacity;
   _attempted                   = that._attempted;
@@ -533,9 +520,9 @@ FixedBufferWriter::operator=(FixedBufferWriter &&that)
 }
 
 inline FixedBufferWriter &
-FixedBufferWriter::write(char c)
-{
-  if (_attempted < _capacity) {
+FixedBufferWriter::write(char c) {
+  if (_attempted < _capacity)
+  {
     _buffer[_attempted] = c;
   }
   ++_attempted;
@@ -544,16 +531,16 @@ FixedBufferWriter::write(char c)
 }
 
 inline FixedBufferWriter &
-FixedBufferWriter::write(const void *data, size_t length)
-{
+FixedBufferWriter::write(const void *data, size_t length) {
   const size_t newSize = _attempted + length;
 
-  if (_buffer) {
-    if (newSize <= _capacity) {
+  if (_buffer)
+  {
+    if (newSize <= _capacity)
+    {
       std::memcpy(_buffer + _attempted, data, length);
-    } else if (_attempted < _capacity) {
-      std::memcpy(_buffer + _attempted, data, _capacity - _attempted);
-    }
+    } else if (_attempted < _capacity)
+    { std::memcpy(_buffer + _attempted, data, _capacity - _attempted); }
   }
   _attempted = newSize;
 
@@ -562,47 +549,41 @@ FixedBufferWriter::write(const void *data, size_t length)
 
 /// Return the output buffer.
 inline const char *
-FixedBufferWriter::data() const
-{
+FixedBufferWriter::data() const {
   return _buffer;
 }
 
 inline bool
-FixedBufferWriter::error() const
-{
+FixedBufferWriter::error() const {
   return _attempted > _capacity;
 }
 
 inline char *
-FixedBufferWriter::aux_data()
-{
+FixedBufferWriter::aux_data() {
   return error() ? nullptr : _buffer + _attempted;
 }
 
 inline bool
-FixedBufferWriter::commit(size_t n)
-{
+FixedBufferWriter::commit(size_t n) {
   _attempted += n;
 
   return true;
 }
 
 inline size_t
-FixedBufferWriter::capacity() const
-{
+FixedBufferWriter::capacity() const {
   return _capacity;
 }
 
 inline size_t
-FixedBufferWriter::extent() const
-{
+FixedBufferWriter::extent() const {
   return _attempted;
 }
 
 inline auto
-FixedBufferWriter::restrict(size_t n) -> self_type &
-{
-  if (n > _capacity) {
+FixedBufferWriter::restrict(size_t n) -> self_type & {
+  if (n > _capacity)
+  {
     throw(std::invalid_argument{"FixedBufferWriter restrict value more than capacity"});
   }
   _capacity -= n;
@@ -610,9 +591,9 @@ FixedBufferWriter::restrict(size_t n) -> self_type &
 }
 
 inline auto
-FixedBufferWriter::restore(size_t n) -> self_type &
-{
-  if (error()) {
+FixedBufferWriter::restore(size_t n) -> self_type & {
+  if (error())
+  {
     _attempted = _capacity;
   }
   _capacity += n;
@@ -620,22 +601,19 @@ FixedBufferWriter::restore(size_t n) -> self_type &
 }
 
 inline auto
-FixedBufferWriter::discard(size_t n) -> self_type &
-{
+FixedBufferWriter::discard(size_t n) -> self_type & {
   _attempted -= std::min(_attempted, n);
   return *this;
 }
 
 inline auto
-FixedBufferWriter::clear() -> self_type &
-{
+FixedBufferWriter::clear() -> self_type & {
   _attempted = 0;
   return *this;
 }
 
 inline auto
-FixedBufferWriter::copy(size_t dst, size_t src, size_t n) -> self_type &
-{
+FixedBufferWriter::copy(size_t dst, size_t src, size_t n) -> self_type & {
   auto limit = std::min<size_t>(_capacity, _attempted); // max offset of region possible.
   MemSpan<char> src_span{_buffer + src, std::min(limit, src + n)};
   MemSpan<char> dst_span{_buffer + dst, std::min(limit, dst + n)};
@@ -644,14 +622,12 @@ FixedBufferWriter::copy(size_t dst, size_t src, size_t n) -> self_type &
 }
 
 inline std::string_view
-FixedBufferWriter::view() const
-{
+FixedBufferWriter::view() const {
   return {_buffer, size()};
 }
 
 /// Provide a @c string_view of all successfully written characters as a user conversion.
-inline FixedBufferWriter::operator std::string_view() const
-{
+inline FixedBufferWriter::operator std::string_view() const {
   return this->view();
 }
 
@@ -663,8 +639,7 @@ template <size_t N> LocalBufferWriter<N>::LocalBufferWriter() : super_type(_arr,
 namespace std
 {
 inline ostream &
-operator<<(ostream &s, swoc::BufferWriter const &w)
-{
+operator<<(ostream &s, swoc::BufferWriter const &w) {
   return w >> s;
 }
 } // end namespace std
diff --git a/swoc++/include/swoc/DiscreteRange.h b/swoc++/include/swoc/DiscreteRange.h
new file mode 100644
index 0000000..b8914ff
--- /dev/null
+++ b/swoc++/include/swoc/DiscreteRange.h
@@ -0,0 +1,1305 @@
+#pragma once
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014 Network Geographics
+
+/** @file
+    Support classes for creating intervals of numeric values.
+
+    The template class can be used directly via a @c typedef or
+    used as a base class if additional functionality is required.
+ */
+
+#include <limits>
+#include <functional>
+
+#include <swoc/swoc_meta.h>
+#include <swoc/RBTree.h>
+#include <swoc/MemArena.h>
+
+namespace swoc
+{
+/// Internal implementation namespace.
+namespace detail
+{
+
+  /// A set of metafunctions to get extrema from a metric type.
+  /// These probe for a static member and falls back to @c std::numeric_limits.
+  /// @{
+  template <typename M>
+  constexpr auto
+  maximum(meta::CaseTag<0>) -> M {
+    return std::numeric_limits<M>::max();
+  }
+
+  template <typename M>
+  constexpr auto
+  maximum(meta::CaseTag<1>) -> decltype(M::MAX) {
+    return M::MAX;
+  }
+
+  template <typename M>
+  constexpr M
+  maximum() {
+    return maximum<M>(meta::CaseArg);
+  }
+
+  template <typename M>
+  constexpr auto
+  minimum(meta::CaseTag<0>) -> M {
+    return std::numeric_limits<M>::min();
+  }
+
+  template <typename M>
+  constexpr auto
+  minimum(meta::CaseTag<1>) -> decltype(M::MIN) {
+    return M::MIN;
+  }
+
+  template <typename M>
+  constexpr M
+  minimum() {
+    return minimum<M>(meta::CaseArg);
+  }
+  /// @}
+} // namespace detail
+
+/// Relationship between two intervals.
+enum DiscreteRangeRelation {
+  NONE,     ///< No common elements.
+  EQUAL,    ///< Identical ranges.
+  SUBSET,   ///< All elements in LHS are also in RHS.
+  SUPERSET, ///< Every element in RHS is in LHS.
+  OVERLAP,  ///< There exists at least one element in both LHS and RHS.
+  ADJACENT  ///< The two intervals are adjacent and disjoint.
+};
+
+/** A range over a discrete finite value metric.
+   @tparam T The type for the range values.
+
+   The template argument @a T is presumed to
+   - be completely ordered.
+   - have prefix increment and decrement operators
+   - equality operator
+   - have value semantics
+   - have minimum and maximum values either
+     - members @c MIN and @c MAX that define static instances
+     - support @c std::numeric_limits<T>
+
+   The interval is always an inclusive (closed) contiguous interval,
+   defined by the minimum and maximum values contained in the interval.
+   An interval can be @em empty and contain no values. This is the state
+   of a default constructed interval.
+ */
+template <typename T> class DiscreteRange {
+  using self_type   = DiscreteRange;
+
+protected:
+  T _min; ///< The minimum value in the interval
+  T _max; ///< the maximum value in the interval
+
+public:
+  using metric_type = T;
+  using Relation = DiscreteRangeRelation;
+
+  static constexpr self_type ALL{detail::minimum<metric_type>(), detail::maximum<metric_type>()};
+
+  /** Default constructor.
+      An empty range is constructed.
+   */
+  constexpr DiscreteRange() : _min(detail::maximum<T>()), _max(detail::minimum<T>()) {}
+
+  /** Construct a singleton range.
+   * @param value Single value to be contained by the interval.
+   *
+   * @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){};
+
+  /** Constructor.
+   *
+   * @param min Minimum value in the interval.
+   * @param max Maximum value in the interval.
+   */
+  constexpr DiscreteRange(T const &min, T const &max) : _min(min), _max(max) {}
+
+  ~DiscreteRange() = default;
+
+  self_type &assign(metric_type const &min, metric_type const &max);
+
+  /// Set the interval to be a singleton.
+  self_type &assign(metric_type const &singleton);
+
+  self_type &assign_min(metric_type const &min);
+
+  self_type &assign_max(metric_type const &max);
+
+  /** Decrement the maximum value.
+   *
+   * @return @a this.
+   */
+  self_type &clip_max() { --_max; return *this; }
+
+  /** Get the minimum value in the interval.
+      @note The return value is unspecified if the interval is empty.
+   */
+  metric_type const &min() const;
+
+  /** Get the maximum value in the interval.
+      @note The return value is unspecified if the interval is empty.
+   */
+  metric_type const &max() const;
+
+  bool contains(metric_type const& n) {
+    return _min <= n && n <= _max;
+  }
+
+  /** Logical intersection test for two intervals.
+      @return @c true if there is at least one common value in the
+      two intervals, @c false otherwise.
+  */
+  bool has_intersection_with(self_type const &that) const;
+
+  /** Compute the intersection of two intervals
+      @return The interval consisting of values that are contained by
+      both intervals. This may be the empty interval if the intervals
+      are disjoint.
+      @internal Co-variant
+   */
+  self_type intersection(self_type const &that) const;
+
+  /** Test for adjacency.
+      @return @c true if the intervals are adjacent.
+      @note Only disjoint intervals can be adjacent.
+   */
+  bool is_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;
+
+  /** Test if an interval is a superset of or equal to another.
+      @return @c true if every value in @c that is also in @c this.
+   */
+  bool is_superset_of(self_type const &that) const;
+
+  /** Test if an interval is a subset or equal to another.
+      @return @c true if every value in @c this is also in @c that.
+   */
+  bool is_subset_of(self_type const &that) const;
+
+  /** Test if an interval is a strict superset of another.
+      @return @c true if @c this is strictly a superset of @a rhs.
+   */
+  bool is_strict_superset_of(self_type const &that) const;
+
+  /** Test if an interval is a strict subset of another.
+      @return @c true if @c this is strictly a subset of @a that.
+   */
+  bool is_strict_subset_of(self_type const &that) const;
+
+  /** Determine the relationship between @c this and @a that interval.
+      @return The relationship type.
+   */
+  Relation relationship(self_type const &that) const;
+
+  /** 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.
+      @internal Co-variant
+   */
+  self_type hull(self_type const &that) const;
+
+  //! Check if the interval is exactly one element.
+  bool is_singleton() const;
+
+  //! Check if the interval is empty.
+  bool is_empty() const;
+
+  /** Test for empty, operator form.
+      @return @c true if the interval is empty, @c false otherwise.
+   */
+  bool operator!() const { return _min > _max; }
+
+  /** Test for non-empty.
+   *
+   * @return @c true if there values in the range, @c false if no values in the range.
+   */
+  explicit operator bool() const { return _min <= _max; }
+
+  /// @return @c true if the range is maximal, @c false otherwise.
+  bool is_maximal() const;
+
+  /** Clip interval.
+      Remove all element in @c this interval not in @a that interval.
+   */
+  self_type &operator&=(self_type const &that);
+
+  /** Convex hull.
+      Extend interval to cover all elements in @c this and @a that.
+   */
+  self_type &operator|=(self_type const &that);
+
+  self_type & clear() {
+    _min = detail::maximum<T>();
+    _max = detail::minimum<T>();
+    return *this;
+  }
+  /** Functor for lexicographic ordering.
+      If, for some reason, an interval needs to be put in a container
+      that requires a strict weak ordering, the default @c operator @c < will
+      not work. Instead, this functor should be used as the comparison
+      functor. E.g.
+      @code
+      typedef std::set<Interval<T>, Interval<T>::lexicographic_order> container;
+      @endcode
+
+      @note Lexicographic ordering is a standard tuple ordering where the
+      order is determined by pairwise comparing the elements of both tuples.
+      The first pair of elements that are not equal determine the ordering
+      of the overall tuples.
+   */
+  struct lexicographic_order : public std::binary_function<self_type, self_type, bool> {
+    //! Functor operator.
+    bool operator()(self_type const &lhs, self_type const &rhs) const;
+  };
+};
+
+template <typename T>
+bool
+DiscreteRange<T>::lexicographic_order::operator()(DiscreteRange::self_type const &lhs, DiscreteRange::self_type const &rhs) const {
+  return lhs._min == rhs._min ? lhs._max < rhs._max : lhs._min < rhs._min;
+}
+
+template <typename T>
+DiscreteRange<T> &
+DiscreteRange<T>::assign(metric_type const &min, metric_type const &max) {
+  _min = min;
+  _max = max;
+  return *this;
+}
+
+template <typename T>
+DiscreteRange<T>
+DiscreteRange<T>::hull(DiscreteRange::self_type const &that) const {
+  // need to account for invalid ranges.
+  return !*this ? that : !that ? *this : self_type(std::min(_min, that._min), std::max(_max, that._max));
+}
+
+template <typename T>
+typename DiscreteRange<T>::Relation
+DiscreteRange<T>::relationship(self_type const &that) const {
+  Relation retval = NONE;
+  if (this->has_intersection(that)) {
+    if (*this == that)
+      retval = EQUAL;
+    else if (this->is_subset_of(that))
+      retval = SUBSET;
+    else if (this->is_superset_of(that))
+      retval = SUPERSET;
+    else
+      retval = OVERLAP;
+  } else if (this->is_adjacent_to(that)) {
+    retval = ADJACENT;
+  }
+  return retval;
+}
+
+template <typename T>
+DiscreteRange<T> &
+DiscreteRange<T>::assign(metric_type const &singleton) {
+  _min = singleton;
+  _max = singleton;
+  return *this;
+}
+
+template <typename T>
+DiscreteRange<T> &
+DiscreteRange<T>::assign_min(metric_type const &min) {
+  _min = min;
+  return *this;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_singleton() const {
+  return _min == _max;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_empty() const {
+  return _min > _max;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_maximal() const {
+  return _min == detail::minimum<T>() && _max == detail::maximum<T>();
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_strict_superset_of(DiscreteRange::self_type const &that) const {
+  return (_min < that._min && that._max <= _max) || (_min <= that._min && that._max < _max);
+}
+
+template <typename T>
+DiscreteRange<T> &
+DiscreteRange<T>::operator|=(DiscreteRange::self_type const &that) {
+  if (!*this) {
+    *this = that;
+  } else if (that) {
+    if (that._min < _min) {
+      _min = that._min;
+    }
+    if (that._max > _max) {
+      _max = that._max;
+    }
+  }
+  return *this;
+}
+
+template <typename T>
+DiscreteRange<T> &
+DiscreteRange<T>::assign_max(metric_type const &max) {
+  _max = max;
+  return *this;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_strict_subset_of(DiscreteRange::self_type const &that) const {
+  return that.is_strict_superset_of(*this);
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_subset_of(DiscreteRange::self_type const &that) const {
+  return that.is_superset_of(*this);
+}
+
+template <typename T>
+T const &
+DiscreteRange<T>::min() const {
+  return _min;
+}
+
+template <typename T>
+T const &
+DiscreteRange<T>::max() const {
+  return _max;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::has_union(DiscreteRange::self_type const &that) const {
+  return this->has_intersection(that) || this->is_adjacent_to(that);
+}
+
+template <typename T>
+DiscreteRange<T> &
+DiscreteRange<T>::operator&=(DiscreteRange::self_type const &that) {
+  *this = this->intersection(that);
+  return *this;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::has_intersection_with(DiscreteRange::self_type const &that) const {
+  return (that._min <= _min && _min <= that._max) || (_min <= that._min && that._min <= _max);
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_superset_of(DiscreteRange::self_type const &that) const {
+  return _min <= that._min && that._max <= _max;
+}
+
+template <typename T>
+bool
+DiscreteRange<T>::is_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
+   * T has a modulus and not depend on ++t > t always being true. However, we know that if t1 >
+   * t0 then ++t0 > t0.
+   */
+
+  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;
+}
+
+template <typename T>
+DiscreteRange<T>
+DiscreteRange<T>::intersection(DiscreteRange::self_type const &that) const {
+  return {std::max(_min, that._min), std::min(_max, that._max)};
+}
+
+/** Equality.
+    Two intervals are equal if their min and max values are equal.
+    @relates interval
+ */
+template <typename T>
+bool
+operator==(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return lhs.min() == rhs.min() && lhs.max() == rhs.max();
+}
+
+/** Inequality.
+    Two intervals are equal if their min and max values are equal.
+    @relates interval
+ */
+template <typename T>
+bool
+operator!=(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return !(lhs == rhs);
+}
+
+/** Operator form of logical intersection test for two intervals.
+    @return @c true if there is at least one common value in the
+    two intervals, @c false otherwise.
+    @note Yeah, a bit ugly, using an operator that is not standardly
+    boolean but
+    - There don't seem to be better choices (&&,|| not good)
+    - The assymmetry between intersection and union makes for only three natural operators
+    - ^ at least looks like "intersects"
+    @relates interval
+ */
+template <typename T>
+bool
+operator^(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return lhs.has_intersection(rhs);
+}
+
+/** Containment ordering.
+    @return @c true if @c this is a strict subset of @a rhs.
+    @note Equivalent to @c is_strict_subset.
+    @relates interval
+ */
+template <typename T>
+inline bool
+operator<(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return rhs.is_strict_superset_of(lhs);
+}
+
+/** Containment ordering.
+    @return @c true if @c this is a subset of @a rhs.
+    @note Equivalent to @c is_subset.
+    @relates interval
+ */
+template <typename T>
+inline bool
+operator<=(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return rhs.is_superset_of(lhs);
+}
+
+/** Containment ordering.
+    @return @c true if @c this is a strict superset of @a rhs.
+    @note Equivalent to @c is_strict_superset.
+    @relates interval
+ */
+template <typename T>
+inline bool
+operator>(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return lhs.is_strict_superset_of(rhs);
+}
+
+/** Containment ordering.
+    @return @c true if @c this is a superset of @a rhs.
+    @note Equivalent to @c is_superset.
+    @relates interval
+    */
+template <typename T>
+inline bool
+operator>=(DiscreteRange<T> const &lhs, DiscreteRange<T> const &rhs) {
+  return lhs.is_superset_of(rhs);
+}
+
+/** A space for a discrete @c METRIC.
+ *
+ * @tparam METRIC Value type for the space.
+ * @tparam PAYLOAD Data stored with values in the space.
+ *
+ * This is a range based mapping of all values in @c METRIC (the "space") to @c PAYLOAD.
+ *
+ * @c PAYLOAD is presumed to be relatively cheap to construct and copy.
+ *
+ * @c METRIC must be
+ * - discrete and finite valued type with increment and decrement operations.
+ */
+template <typename METRIC, typename PAYLOAD> class DiscreteSpace {
+  using self_type = DiscreteSpace;
+
+protected:
+  using metric_type  = METRIC;  ///< Export.
+  using payload_type = PAYLOAD; ///< Export.
+  using range_type   = DiscreteRange<METRIC>;
+
+  /// A node in the range tree.
+  class Node : public detail::RBNode {
+    using self_type  = Node;           ///< Self reference type.
+    using super_type = detail::RBNode; ///< Parent class.
+    friend class DiscreteSpace;
+
+    PAYLOAD _payload{}; ///< Default constructor, should zero init if @c PAYLOAD is a pointer.
+    range_type _range;  ///< Range covered by this node.
+    range_type _hull;   ///< Range covered by subtree rooted at this node.
+
+  public:
+    /// Linkage for @c IntrusiveDList.
+    using Linkage = swoc::IntrusiveLinkageRebind<self_type, super_type::Linkage>;
+
+    Node() = default; ///< Construct empty node.
+
+    /// Construct from @a range and @a payload.
+    Node(range_type const &range, PAYLOAD const &payload) : _range(range), _payload(payload) {}
+    /// Construct from two metrics and a payload
+    Node(METRIC const& min, METRIC const& max, PAYLOAD const& payload) : _range(min, max), _payload(payload) {}
+
+    /// @return The payload in the node.
+    PAYLOAD & payload();
+
+    /** Set the @a range of a node.
+     *
+     * @param range Range to use.
+     * @return @a this
+     */
+    self_type & assign(range_type const &range);
+
+    /** Set the @a payload for @a this node.
+     *
+     * @param payload Payload to use.
+     * @return @a this
+     */
+    self_type & assign(PAYLOAD const &payload);
+
+    self_type &
+    assign_min(METRIC const &m) {
+      _range.assign_min(m);
+      this->ripple_structure_fixup();
+      return *this;
+    }
+
+    self_type &
+    assign_max(METRIC const &m) {
+      _range.assign_max(m);
+      this->ripple_structure_fixup();
+      return *this;
+    }
+
+    /** Decrement the maximum value in the range.
+     *
+     * The range for the node is reduced by one.
+     *
+     * @return @a this
+     */
+    self_type &
+    dec_max() {
+      _range.dec_max();
+      this->ripple_structure_fixup();
+      return *this;
+    }
+
+    METRIC const&
+    min() const {
+      return _range.min();
+    }
+    METRIC const&
+    max() const {
+      return _range.max();
+    }
+
+    void structure_fixup() override;
+
+    self_type * left() { return static_cast<self_type*>(_left); }
+    self_type * right() { return static_cast<self_type*>(_right); }
+
+  };
+
+  using Direction = typename Node::Direction;
+
+  Node *_root = nullptr;                        ///< Root node.
+  IntrusiveDList<typename Node::Linkage> _list; ///< In order list of nodes.
+  swoc::MemArena _arena{4000}; ///< Memory Storage.
+  swoc::FixedArena<Node> _fa{_arena}; ///< Node allocator and free list.
+
+  // Utility methods to avoid having casts scattered all over.
+  Node *
+  prev(Node *n) {
+    return Node::Linkage::prev_ptr(n);
+  }
+  Node *
+  next(Node *n) {
+    return Node::Linkage::next_ptr(n);
+  }
+  Node *
+  left(Node *n) {
+    return static_cast<Node *>(n->_left);
+  }
+  Node *
+  right(Node *n) {
+    return static_cast<Node *>(n->_right);
+  }
+
+public:
+  using iterator = typename decltype(_list)::iterator;
+
+  DiscreteSpace() = default;
+  ~DiscreteSpace();
+
+  /** Set the @a payload for a @a range
+   *
+   * @param range Range to mark.
+   * @param payload Payload to set.
+   * @return @a this
+   *
+   * Values in @a range are set to @a payload regardless of the current state.
+   */
+  self_type &mark(range_type const &range, PAYLOAD const &payload);
+
+  /** Erase a @a range.
+   *
+   * @param range Range to erase.
+   * @return @a this
+   *
+   * All values in @a range are removed from the space.
+   */
+  self_type &erase(range_type const &range);
+
+  /** Blend a @a payload to a @a range.
+   *
+   * @tparam blender Functor to blend payloads.
+   * @param range Range for blending.
+   * @param payload Payload to blend.
+   * @return @a this
+   *
+   * @a payload is blended to values in @a range. If a value is not present, its payload is set
+   * to @a payload. If the value is present, its payload is set to the blend of the existing payload
+   * and @a payload using @blender. The existing payload is passed as the first argument and @a
+   * payload as the second argument. The functor is expected to update the first argument to be the
+   * blended payload. The function must return a @c bool to indicate whether the blend resulted in
+   * a valid color. If @c false is returned, the blended region is removed from the space.
+   */
+  template < typename F >
+  self_type &blend(range_type const &range, PAYLOAD const &payload, F && blender);
+
+  /** Fill @a range with @a payload.
+   *
+   * @param range Range to fill.
+   * @param payload Payload to use.
+   * @return @a this
+   *
+   * Values in @a range that do not have a payload are set to @a payload. Values in the space are
+   * not changed.
+   */
+  self_type &fill(range_type const &range, PAYLOAD const &payload);
+
+  PAYLOAD * find(METRIC const &addr);
+
+  /// @return The number of distinct ranges.
+  size_t count() const;
+
+  iterator begin() { return _list.begin(); }
+  iterator end() { return _list.end(); }
+
+  /// Remove all ranges.
+  void clear() {
+    for (auto &node : _list) {
+      std::destroy_at(&node.payload());
+    }
+    _list.clear();
+    _root = nullptr;
+    _arena.clear();
+    _fa.clear();
+  }
+
+protected:
+  /** Find the lower bound range for @a target.
+   *
+   * @param target Lower bound value.
+   * @return The rightmost range that starts at or before @a target, or @c nullptr if all ranges start
+   * after @a target.
+   */
+  Node *lower_bound(METRIC const &target);
+
+  /// @return The first node in the tree.
+  Node * head();
+
+  /** Insert @a node before @a spot.
+   *
+   * @param spot Target node.
+   * @param node Node to insert.
+   */
+  void insert_before(Node *spot, Node *node);
+
+  /** Insert @a node after @a spot.
+   *
+   * @param spot Target node.
+   * @param node Node to insert.
+   */
+  void insert_after(Node *spot, Node *node);
+
+  void prepend(Node *node);
+
+  void append(Node *node);
+
+  void
+  remove(Node *node) {
+    _root = static_cast<Node *>(node->remove());
+    _list.erase(node);
+    _fa.destroy(node);
+  }
+};
+
+// ---
+
+template <typename METRIC, typename PAYLOAD>
+PAYLOAD &
+DiscreteSpace<METRIC, PAYLOAD>::Node::payload() {
+  return _payload;
+}
+
+template <typename METRIC, typename PAYLOAD>
+auto
+DiscreteSpace<METRIC, PAYLOAD>::Node::assign(DiscreteSpace::range_type const &range) -> self_type &{
+  _range = range;
+  return *this;
+}
+
+template <typename METRIC, typename PAYLOAD>
+auto
+DiscreteSpace<METRIC, PAYLOAD>::Node::assign(PAYLOAD const &payload) -> self_type & {
+  _payload = payload;
+  return *this;
+}
+
+template<typename METRIC, typename PAYLOAD>
+void DiscreteSpace<METRIC, PAYLOAD>::Node::structure_fixup() {
+  if (_left) {
+    if (_right) {
+      _hull = this->left()->_range.hull(this->right()->_range);
+    } else {
+      _hull = this->left()->_range;
+    }
+  } else if (_right) {
+    _hull = this->right()->_range;
+  }
+}
+
+// ---
+
+template <typename METRIC, typename PAYLOAD>
+DiscreteSpace<METRIC, PAYLOAD>::~DiscreteSpace() {
+  // Destruct all the payloads - the nodes themselves are in the arena and disappear with it.
+  for (auto &node : _list) {
+    std::destroy_at(&node.payload());
+  }
+}
+
+template <typename METRIC, typename PAYLOAD>
+size_t DiscreteSpace<METRIC, PAYLOAD>::count() const { return _list.count(); }
+
+template <typename METRIC, typename PAYLOAD>
+auto
+DiscreteSpace<METRIC, PAYLOAD>::head() -> Node *{
+  return static_cast<Node *>(_list.head());
+}
+
+template <typename METRIC, typename PAYLOAD>
+PAYLOAD *
+DiscreteSpace<METRIC, PAYLOAD>::find(METRIC const &addr) {
+  auto n = _root; // current node to test.
+  while (n) {
+    if (addr < n->min()) {
+      if (n->_hull.contains(addr)) {
+        n = n->left();
+      } else {
+        return nullptr;
+      }
+    } else if (n->max() < addr) {
+      if (n->_hull.contains(addr)) {
+        n = n->right();
+      } else {
+        return nullptr;
+      }
+    } else {
+      return &n->payload();
+    }
+  }
+  return nullptr;
+}
+
+template <typename METRIC, typename PAYLOAD>
+auto DiscreteSpace<METRIC, PAYLOAD>::lower_bound(METRIC const &target) -> Node * {
+  Node *n    = _root;   // current node to test.
+  Node *zret = nullptr; // best node so far.
+  while (n) {
+    if (target < n->min()) {
+      n = left(n);
+    } else {
+      zret = n; // this is a better candidate.
+      if (n->max() < target) {
+        n = right(n);
+      } else {
+        break;
+      }
+    }
+  }
+  return zret;
+}
+
+template<typename METRIC, typename PAYLOAD>
+void DiscreteSpace<METRIC, PAYLOAD>::prepend(DiscreteSpace::Node *node) {
+  if (!_root) {
+    _root = node;
+  } else {
+    _root = static_cast<Node *>(_list.head()->set_child(node, Direction::LEFT)->rebalance_after_insert());
+  }
+  _list.prepend(node);
+}
+
+template<typename METRIC, typename PAYLOAD>
+void DiscreteSpace<METRIC, PAYLOAD>::append(DiscreteSpace::Node *node) {
+  if (!_root) {
+    _root = node;
+  } else {
+    // The last node has no right child, or it wouldn't be the last.
+    _root = static_cast<Node *>(_list.tail()->set_child(node, Direction::RIGHT)->rebalance_after_insert());
+  }
+  _list.append(node);
+}
+
+template <typename METRIC, typename PAYLOAD>
+void
+DiscreteSpace<METRIC, PAYLOAD>::insert_before(DiscreteSpace::Node *spot, DiscreteSpace::Node *node) {
+  if (left(spot) == nullptr) {
+    spot->set_child(node, Direction::LEFT);
+  } else {
+    // If there's a left child, there's a previous node, therefore spot->_prev is valid.
+    // Further, the previous node must be the rightmost descendant node of the left subtree
+    // and therefore has no current right child.
+    spot->_prev->set_child(node, Direction::RIGHT);
+  }
+
+  _list.insert_before(spot, node);
+  _root = static_cast<Node *>(node->rebalance_after_insert());
+}
+
+template <typename METRIC, typename PAYLOAD>
+void
+DiscreteSpace<METRIC, PAYLOAD>::insert_after(DiscreteSpace::Node *spot, DiscreteSpace::Node *node) {
+  Node *c = right(spot);
+  if (!c) {
+    spot->set_child(node, Direction::RIGHT);
+  } else {
+    // If there's a right child, there's a successor node, and therefore @a _next is valid.
+    // Further, the successor node must be the left most descendant of the right subtree
+    // therefore it doesn't have a left child.
+    spot->_next->set_child(node, Direction::LEFT);
+  }
+
+  _list.insert_after(spot, node);
+  _root = static_cast<Node *>(node->rebalance_after_insert());
+}
+
+template <typename METRIC, typename PAYLOAD>
+DiscreteSpace<METRIC, PAYLOAD> &
+DiscreteSpace<METRIC, PAYLOAD>::mark(DiscreteSpace::range_type const &range, PAYLOAD const &payload) {
+  Node *n = this->lower_bound(range.min()); // current node.
+  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.
+
+  /*  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;
+    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
+      // if there is a previous range, min is not zero.
+      Node *p = prev(n);
+      if (p && p->payload() == payload && p->max() == min_minus_1) {
+        x = p;
+        n = x; // need to back up n because frame of reference moved.
+        x->assign_max(range.max());
+      } else if (n->max() <= range.max()) {
+        // Span will be subsumed by request span so it's available for use.
+        x = n;
+        x->assign_max(range.max()).assign(payload);
+      } else if (n->payload() == payload) {
+        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}; //
+        n->assign_min(max_plus_1);    // clip existing.
+        this->insert_before(n, x);
+        return *this;
+      }
+    } else if (n->payload() == payload && n->max() >= min_minus_1) {
+      // min_minus_1 is safe here because n->_min < min so min is not zero.
+      x = n;
+      // If the existing span covers the requested span, we're done.
+      if (x->max() >= range.max()) {
+        return *this;
+      }
+      x->assign_max(range.max());
+    } else if (n->max() <= range.max()) {
+      // Can only have left skew overlap, otherwise disjoint.
+      // Clip if overlap.
+      if (n->max() >= range.min()) {
+        n->assign_max(min_minus_1);
+      } else if (nullptr != (y = next(n)) && y->max() <= range.max()) {
+        // because @a n was selected as the minimum it must be the case that
+        // y->min >= min (or y would have been selected). Therefore in this
+        // case the request covers the next node therefore it can be reused.
+        x = y;
+        x->assign(range).assign(payload);
+        n = x; // this gets bumped again, which is correct.
+      }
+    } else {
+      // Existing span covers new span but with a different payload.
+      // We split it, put the new span in between and we're done.
+      // max_plus_1 is valid because n->_max > max.
+      Node *r;
+      x = _fa.make(range, payload);
+      r = _fa.make(range_type{max_plus_1, n->max()}, n->payload());
+      n->assign_max(min_minus_1);
+      this->insert_after(n, x);
+      this->insert_after(x, r);
+      return *this; // done.
+    }
+    n = next(n); // lower bound span handled, move on.
+    if (!x) {
+      x = _fa.make(range, payload);
+      if (n) {
+        this->insert_before(n, x);
+      } else {
+        this->append(x); // note that since n == 0 we'll just return.
+      }
+    }
+  } else if (nullptr != (n = this->head()) &&                  // at least one node in tree.
+             n->payload() == payload &&                            // payload matches
+             (n->max() <= range.max() || n->min() <= max_plus_1) // overlap or adj.
+  ) {
+    // Same payload with overlap, re-use.
+    x = n;
+    n = next(n);
+    x->assign_min(range.min());
+    if (x->max() < range.max()) {
+      x->assign_max(range.max());
+    }
+  } else {
+    x = _fa.make(range, payload);
+    this->prepend(x);
+  }
+
+  // At this point, @a x has the node for this span and all existing spans of
+  // interest start at or past this span.
+  while (n) {
+    if (n->max() <= range.max()) { // completely covered, drop span, continue
+      y = n;
+      n = next(n);
+      this->remove(y);
+    } else if (max_plus_1 < n->min()) { // no overlap, done.
+      break;
+    } else if (n->payload() == payload) { // skew overlap or adj., same payload
+      x->assign_max(n->max());
+      y = n;
+      n = next(n);
+      this->remove(y);
+    } else if (n->min() <= range.max()) { // skew overlap different payload
+      n->assign_min(max_plus_1);
+      break;
+    }
+  }
+
+  return *this;
+}
+
+template <typename METRIC, typename PAYLOAD>
+DiscreteSpace<METRIC, PAYLOAD> &
+DiscreteSpace<METRIC, PAYLOAD>::fill(DiscreteSpace::range_type const &range, PAYLOAD const &payload) {
+  // Rightmost node of interest with n->_min <= min.
+  Node *n = this->lower_bound(range.min());
+  Node *x = nullptr; // New node (if any).
+  // Need copies because we will modify these.
+  auto min = range.min();
+  auto max = range.max();
+
+  // Handle cases involving a node of interest to the left of the
+  // range.
+  if (n) {
+    if (n->_min < min) {
+      auto min_1 = min;
+      --min_1;               // dec is OK because min isn't zero.
+      if (n->max() < min_1) { // no overlap, not adjacent.
+        n = next(n);
+      } else if (n->max() >= max) { // incoming range is covered, just discard.
+        return *this;
+      } else if (n->payload != payload) { // different payload, clip range on left.
+        min = n->max();
+        ++min;
+        n = next(n);
+      } else { // skew overlap or adjacent to predecessor with same payload, use node and continue.
+        x = n;
+        n = next(n);
+      }
+    }
+  } else {
+    n = this->head();
+  }
+
+  // Work through the rest of the nodes of interest.
+  // Invariant: n->min() >= min
+
+  // Careful here -- because max_plus1 might wrap we need to use it only if we can be certain it
+  // didn't. This is done by ordering the range tests so that when max_plus1 is used when we know
+  // there exists a larger value than max.
+  auto max_plus1 = max;
+  ++max_plus1;
+
+  /* Notes:
+     - max (and thence also max_plus1) never change during the loop.
+     - we must have either x != 0 or adjust min but not both for each loop iteration.
+  */
+  while (n) {
+    if (n->data() == payload) {
+      if (x) {
+        if (n->_max <= max) { // next range is covered, so we can remove and continue.
+          this->remove(n);
+          n = next(x);
+        } else if (n->_min <= max_plus1) {
+          // Overlap or adjacent with larger max - absorb and finish.
+          x->assign_max(n->_max);
+          this->remove(n);
+          return *this;
+        } else {
+          // have the space to finish off the range.
+          x->assign_max(max);
+          return *this;
+        }
+      } else {                // not carrying a span.
+        if (n->_max <= max) { // next range is covered - use it.
+          x = n;
+          x->assign_min(min);
+          n = next(n);
+        } else if (n->_min <= max_plus1) {
+          n->assign_min(min);
+          return *this;
+        } else { // no overlap, space to complete range.
+          this->insert_before(n, _fa.make(min, max, payload));
+          return *this;
+        }
+      }
+    } else { // different payload
+      if (x) {
+        if (max < n->_min) { // range ends before n starts, done.
+          x->assign_max(max);
+          return *this;
+        } else if (max <= n->_max) { // range ends before n, done.
+          x->assign_max(n->_min).dec_max();
+          return *this;
+        } else { // n is contained in range, skip over it.
+          x->assign_max(n->_min).dec_max();
+          x   = nullptr;
+          min = n->_max;
+          ++min; // OK because n->_max maximal => next is null.
+          n = next(n);
+        }
+      } else {               // no carry node.
+        if (max < n->_min) { // entirely before next span.
+          this->insert_before(n, new Node(min, max, payload));
+          return *this;
+        } else {
+          if (min < n->_min) { // leading section, need node.
+            auto y = _fa.make(min, n->_min, payload);
+            y->dec_max();
+            this->insert_before(n, y);
+          }
+          if (max <= n->_max) { // nothing past node
+            return *this;
+          }
+          min = n->_max;
+          ++min;
+          n = next(n);
+        }
+      }
+    }
+  }
+  // Invariant: min is larger than any existing range maximum.
+  if (x) {
+    x->assign_max(max);
+  } else {
+    this->append(_fa.make(min, max, payload));
+  }
+  return *this;
+}
+
+template<typename METRIC, typename PAYLOAD>
+template<typename F>
+auto
+DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, PAYLOAD const&payload
+                                      , F &&blender) -> self_type & {
+  // Do a base check for the color to use on unmapped values. If self blending on @a payload
+  // is @c false, then do not color currently unmapped values.
+  PAYLOAD plain_color { payload };
+  bool plain_color_p = blender(plain_color, plain_color);
+  // Used to hold a temporary blended node - @c release if put in space, otherwise cleaned up.
+  auto node_cleaner = [&] (Node * ptr) -> void { _fa.destroy(ptr); };
+  using unique_node = std::unique_ptr<Node, decltype(node_cleaner)>;
+
+  // Rightmost node of interest with n->_min <= 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();
+
+  if (nullptr == n) {
+    n = this->head();
+  }
+
+  // Process @a n, covering the values from the previous range to @a n.max
+  while (n) {
+    // min is the smallest value of interest, need to fill from there on to the right.
+    metric_type min, min_minus_1;
+
+    pred = n ? prev(n) : nullptr;
+    if (pred && pred->max() >= range.min()) {
+      min_minus_1 = min = pred->max();
+      ++min;
+    } else {
+      min_minus_1 = min = range.min();
+      --min_minus_1;
+      if (pred && pred->max() < min_minus_1) {
+        pred = nullptr;
+      }
+    }
+    // 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;
+    // @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.
+    if (!right_overlap_p) {
+      if (right_adj_p && n_plain_colored_p) { // can pull @a n left to cover
+        n->assign_min(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));
+      }
+      return *this;
+    }
+
+    // Invariant: There is overlap between @a n and @a range.
+
+    // 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 (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);
+        }
+      } else {
+        auto n_min_minus_1{n->min()};
+        --n_min_minus_1;
+        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));
+        }
+      }
+    }
+
+    // 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.
+    unique_node fill { _fa.make(n->min(), max, n->payload()), node_cleaner };
+    bool fill_p = blender(fill->payload(), payload); // 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;
+
+    // Clean up the range for @a n
+    if (fill_p) {
+      if (right_ext_p) {
+        if (n->payload() == fill->payload()) {
+          n->assign_min(fill->min());
+        } else {
+          n->assign_min(range_max_plus_1);
+          this->insert_before(n, fill.release());
+          return *this;
+        }
+      } else {
+        // PROBLEM - not collapsing into @a pred(n) when it's adjacent / matching payload.
+        // But @a pred may have been removed above, not reliable at this point.
+        pred = prev(n);
+        if (pred && pred->payload() == fill->payload()) {
+          this->remove(n);
+          pred->assign_max(fill->max());
+        } else {
+          this->insert_before(n, fill.release());
+          this->remove(n);
+        }
+      }
+    } else {
+      if (right_ext_p) {
+        n->assign_min(range_max_plus_1);
+        return *this;
+      } else {
+        this->remove(n);
+      }
+    }
+
+    // Everything up to @a n.max is correct, time to process next node.
+    n = next_n;
+  }
+
+  // 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()) {
+    // 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) {
+      n->assign_max(range.max());
+    } else {
+      this->append(_fa.make(range_min, range.max(), plain_color));
+    }
+  }
+
+  return *this;
+}
+
+} // namespace swoc
diff --git a/swoc++/include/swoc/Errata.h b/swoc++/include/swoc/Errata.h
index f764342..5cc3db5 100644
--- a/swoc++/include/swoc/Errata.h
+++ b/swoc++/include/swoc/Errata.h
@@ -29,7 +29,7 @@
     so it is even cheaper to copy.
 
     To further ease use, the library has the ability to define @a sinks.  A sink is a function that
-    acts on an erratum when it becomes unreferenced. The indended use is to send the messages to an
+    acts on an erratum when it becomes unreferenced. The intended use is to send the messages to an
     output log. This makes reporting errors to a log from even deeply nested functions easy while
     preserving the ability of the top level logic to control such logging.
 
@@ -73,8 +73,7 @@ enum class Severity : uint8_t {
  * wraps the actual data and can therefore be treated a value type with cheap copy semantics.
  * Default construction is very cheap.
  */
-class Errata
-{
+class Errata {
 public:
   using Severity = swoc::Severity; ///< Import for associated classes.
 
@@ -302,8 +301,7 @@ public:
       destructed until application shutdown). Clients can subclass this class in order to preserve
       arbitrary data for the sink or retain a handle to the sink for runtime modifications.
    */
-  class Sink
-  {
+  class Sink {
     using self_type = Sink;
 
   public:
@@ -332,8 +330,7 @@ public:
 
   /// Register a sink function for abandonded erratum.
   static void
-  register_sink(SinkHandler const &f)
-  {
+  register_sink(SinkHandler const &f) {
     register_sink(Sink::Handle(new SinkWrapper(f)));
   }
 
@@ -385,8 +382,7 @@ extern std::ostream &operator<<(std::ostream &os, Errata const &stat);
     of @c errata rather than having to remember it (and the order) each time - Enable assignment
     directly to @a R for ease of use and compatibility so clients can upgrade asynchronously.
  */
-template <typename R> class Rv
-{
+template <typename R> class Rv {
 public:
   using result_type = R; ///< Type of result value.
 
@@ -582,8 +578,7 @@ template <typename R>
 Rv<typename std::remove_reference<R>::type>
 MakeRv(R &&r,           ///< The function result
        Errata &&erratum ///< The pre-existing status object
-)
-{
+) {
   return Rv<typename std::remove_reference<R>::type>(std::forward<R>(r), std::move(erratum));
 }
 /* ----------------------------------------------------------------------- */
@@ -594,67 +589,57 @@ inline Errata::Annotation::Annotation() {}
 inline Errata::Annotation::Annotation(Severity severity, std::string_view text) : _severity(severity), _text(text) {}
 
 inline Errata::Annotation &
-Errata::Annotation::clear()
-{
+Errata::Annotation::clear() {
   _severity = Errata::DEFAULT_SEVERITY;
   _text     = std::string_view{};
   return *this;
 }
 
 inline std::string_view
-Errata::Annotation::text() const
-{
+Errata::Annotation::text() const {
   return _text;
 }
 
 inline unsigned
-Errata::Annotation::level() const
-{
+Errata::Annotation::level() const {
   return _level;
 }
 
 inline Errata::Severity
-Errata::Annotation::severity() const
-{
+Errata::Annotation::severity() const {
   return _severity;
 }
 
 inline Errata::Annotation &
-Errata::Annotation::assign(std::string_view text)
-{
+Errata::Annotation::assign(std::string_view text) {
   _text = text;
   return *this;
 }
 
 inline Errata::Annotation &
-Errata::Annotation::assign(Severity level)
-{
+Errata::Annotation::assign(Severity level) {
   _severity = level;
   return *this;
 }
 /* ----------------------------------------------------------------------- */
 // Inline methods for Errata::Data
 
-inline Errata::Data::Data(MemArena &&arena)
-{
+inline Errata::Data::Data(MemArena &&arena) {
   _arena = std::move(arena);
 }
 
 inline swoc::MemSpan<char>
-Errata::Data::remnant()
-{
+Errata::Data::remnant() {
   return _arena.remnant().rebind<char>();
 }
 
 inline swoc::MemSpan<char>
-Errata::Data::alloc(size_t n)
-{
+Errata::Data::alloc(size_t n) {
   return _arena.alloc(n).rebind<char>();
 }
 
 inline bool
-Errata::Data::empty() const
-{
+Errata::Data::empty() const {
   return _notes.empty();
 }
 
@@ -663,100 +648,87 @@ Errata::Data::empty() const
 
 inline Errata::Errata() {}
 
-inline Errata::Errata(self_type &&that)
-{
+inline Errata::Errata(self_type &&that) {
   std::swap(_data, that._data);
 }
 
-inline Errata::Errata(self_type const &that)
-{
-  if (nullptr != (_data = that._data)) {
+inline Errata::Errata(self_type const &that) {
+  if (nullptr != (_data = that._data))
+  {
     ++(_data->_ref_count);
   }
 }
 
 inline auto
-Errata::operator=(self_type &&that) -> self_type &
-{
-  if (this != &that) {
+Errata::operator=(self_type &&that) -> self_type & {
+  if (this != &that)
+  {
     this->release();
     std::swap(_data, that._data);
   }
   return *this;
 }
 
-inline Errata::operator bool() const
-{
+inline Errata::operator bool() const {
   return this->is_ok();
 }
 
-inline bool Errata::operator!() const
-{
+inline bool Errata::operator!() const {
   return !this->is_ok();
 }
 
 inline bool
-Errata::empty() const
-{
+Errata::empty() const {
   return _data == nullptr || _data->_notes.count() == 0;
 }
 
 inline size_t
-Errata::count() const
-{
+Errata::count() const {
   return _data ? _data->_notes.count() : 0;
 }
 
 inline bool
-Errata::is_ok() const
-{
+Errata::is_ok() const {
   return 0 == _data || 0 == _data->_notes.count() || _data->_severity < FAILURE_SEVERITY;
 }
 
 inline const Errata::Annotation &
-Errata::front() const
-{
+Errata::front() const {
   return *(_data->_notes.head());
 }
 
 template <typename... Args>
 Errata &
-Errata::note(Severity severity, std::string_view fmt, Args &&... args)
-{
+Errata::note(Severity severity, std::string_view fmt, Args &&... args) {
   return this->note_v(severity, fmt, std::forward_as_tuple(args...));
 }
 
 template <typename... Args>
 Errata &
-Errata::diag(std::string_view fmt, Args &&... args)
-{
+Errata::diag(std::string_view fmt, Args &&... args) {
   return this->note_v(Severity::DIAG, fmt, std::forward_as_tuple(args...));
 }
 
 template <typename... Args>
 Errata &
-Errata::info(std::string_view fmt, Args &&... args)
-{
+Errata::info(std::string_view fmt, Args &&... args) {
   return this->note_v(Severity::INFO, fmt, std::forward_as_tuple(args...));
 }
 
 template <typename... Args>
 Errata &
-Errata::warn(std::string_view fmt, Args &&... args)
-{
+Errata::warn(std::string_view fmt, Args &&... args) {
   return this->note_v(Severity::WARN, fmt, std::forward_as_tuple(args...));
 }
 
 template <typename... Args>
 Errata &
-Errata::error(std::string_view fmt, Args &&... args)
-{
+Errata::error(std::string_view fmt, Args &&... args) {
   return this->note_v(Severity::ERROR, fmt, std::forward_as_tuple(args...));
 }
 
 inline Errata &
-Errata::note(self_type &&that)
-{
+Errata::note(self_type &&that) {
   this->note(that);
   that.clear();
   return *this;
@@ -764,15 +736,16 @@ Errata::note(self_type &&that)
 
 template <typename... Args>
 Errata &
-Errata::note_v(Severity severity, std::string_view fmt, std::tuple<Args...> const &args)
-{
+Errata::note_v(Severity severity, std::string_view fmt, std::tuple<Args...> const &args) {
   Data *data = this->writeable_data();
   auto span  = data->remnant();
   FixedBufferWriter bw{span};
-  if (!bw.print_v(fmt, args).error()) {
+  if (!bw.print_v(fmt, args).error())
+  {
     span = span.prefix(bw.extent());
     data->alloc(bw.extent()); // require the part of the remnant actually used.
-  } else {
+  } else
+  {
     // Not enough space, get a big enough chunk and do it again.
     span = this->alloc(bw.extent());
     FixedBufferWriter{span}.print_v(fmt, args);
@@ -782,8 +755,7 @@ Errata::note_v(Severity severity, std::string_view fmt, std::tuple<Args...> cons
 }
 
 inline void
-Errata::SinkWrapper::operator()(Errata const &e) const
-{
+Errata::SinkWrapper::operator()(Errata const &e) const {
   _f(e);
 }
 /* ----------------------------------------------------------------------- */
@@ -791,15 +763,13 @@ Errata::SinkWrapper::operator()(Errata const &e) const
 
 template <typename R>
 inline bool
-Rv<R>::is_ok() const
-{
+Rv<R>::is_ok() const {
   return _errata.is_ok();
 }
 
 template <typename R>
 inline auto
-Rv<R>::clear() -> self_type &
-{
+Rv<R>::clear() -> self_type & {
   errata().clear();
 }
 
@@ -815,72 +785,62 @@ template <typename R> Rv<R>::Rv(R &&r, Errata &&errata) : _r(std::move(r)), _err
 
 template <typename R> Rv<R>::Rv(Errata &&errata) : _errata{std::move(errata)} {}
 
-template <typename T> Rv<T>::operator result_type const &() const
-{
+template <typename T> Rv<T>::operator result_type const &() const {
   return _r;
 }
 
 template <typename R>
 R const &
-Rv<R>::result() const
-{
+Rv<R>::result() const {
   return _r;
 }
 
 template <typename R>
 R &
-Rv<R>::result()
-{
+Rv<R>::result() {
   return _r;
 }
 
 template <typename R>
 Errata const &
-Rv<R>::errata() const
-{
+Rv<R>::errata() const {
   return _errata;
 }
 
 template <typename R>
 Errata &
-Rv<R>::errata()
-{
+Rv<R>::errata() {
   return _errata;
 }
 
-template <typename R> Rv<R>::operator Errata &()
-{
+template <typename R> Rv<R>::operator Errata &() {
   return _errata;
 }
 
 template <typename R>
 Rv<R> &
-Rv<R>::assign(result_type const &r)
-{
+Rv<R>::assign(result_type const &r) {
   _r = r;
   return *this;
 }
 
 template <typename R>
 Rv<R> &
-Rv<R>::assign(R &&r)
-{
+Rv<R>::assign(R &&r) {
   _r = std::move(r);
   return *this;
 }
 
 template <typename R>
 auto
-Rv<R>::operator=(Errata &&errata) -> self_type &
-{
+Rv<R>::operator=(Errata &&errata) -> self_type & {
   _errata = std::move(errata);
   return *this;
 }
 
 template <typename R>
 auto
-Rv<R>::note(Severity level, std::string_view text) -> self_type &
-{
+Rv<R>::note(Severity level, std::string_view text) -> self_type & {
   _errata.note(level, text);
   return *this;
 }
@@ -888,24 +848,21 @@ Rv<R>::note(Severity level, std::string_view text) -> self_type &
 template <typename R>
 template <typename... Args>
 Rv<R> &
-Rv<R>::note(Severity level, std::string_view fmt, Args &&... args)
-{
+Rv<R>::note(Severity level, std::string_view fmt, Args &&... args) {
   _errata.note_v(level, fmt, std::forward_as_tuple(args...));
   return *this;
 }
 
 template <typename R>
 auto
-Rv<R>::operator=(result_type const &r) -> result_type &
-{
+Rv<R>::operator=(result_type const &r) -> result_type & {
   _r = r;
   return _r;
 }
 
 template <typename R>
 auto
-Rv<R>::operator=(result_type &&r) -> result_type &
-{
+Rv<R>::operator=(result_type &&r) -> result_type & {
   _r = std::move(r);
   return _r;
 }
@@ -921,26 +878,19 @@ BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, Errata const &);
 // Tuple / structured binding support.
 namespace std
 {
-template <size_t IDX, typename R> class tuple_element<IDX, swoc::Rv<R>>
-{
-  static_assert("swoc:Rv tuple index out of range");
-};
+template <size_t IDX, typename R> class tuple_element<IDX, swoc::Rv<R>> { static_assert("swoc:Rv tuple index out of range"); };
 
-template <typename R> class tuple_element<0, swoc::Rv<R>>
-{
+template <typename R> class tuple_element<0, swoc::Rv<R>> {
 public:
   using type = typename swoc::Rv<R>::result_type;
 };
 
-template <typename R> class tuple_element<1, swoc::Rv<R>>
-{
+template <typename R> class tuple_element<1, swoc::Rv<R>> {
 public:
   using type = swoc::Errata;
 };
 
-template <typename R> class tuple_size<swoc::Rv<R>> : public std::integral_constant<size_t, 2>
-{
-};
+template <typename R> class tuple_size<swoc::Rv<R>> : public std::integral_constant<size_t, 2> {};
 
 } // namespace std
 
@@ -954,35 +904,32 @@ namespace swoc
 
 template <size_t IDX, typename R>
 typename std::tuple_element<IDX, swoc::Rv<R>>::type &
-get(swoc::Rv<R> &&rv)
-{
-  if constexpr (IDX == 0) {
+get(swoc::Rv<R> &&rv) {
+  if constexpr (IDX == 0)
+  {
     return rv.result();
-  } else if constexpr (IDX == 1) {
-    return rv.errata();
-  }
+  } else if constexpr (IDX == 1)
+  { return rv.errata(); }
 }
 
 template <size_t IDX, typename R>
 typename std::tuple_element<IDX, swoc::Rv<R>>::type &
-get(swoc::Rv<R> &rv)
-{
-  if constexpr (IDX == 0) {
+get(swoc::Rv<R> &rv) {
+  if constexpr (IDX == 0)
+  {
     return rv.result();
-  } else if constexpr (IDX == 1) {
-    return rv.errata();
-  }
+  } else if constexpr (IDX == 1)
+  { return rv.errata(); }
 }
 
 template <size_t IDX, typename R>
 typename std::tuple_element<IDX, swoc::Rv<R>>::type const &
-get(swoc::Rv<R> const &rv)
-{
-  if constexpr (IDX == 0) {
+get(swoc::Rv<R> const &rv) {
+  if constexpr (IDX == 0)
+  {
     return rv.result();
-  } else if constexpr (IDX == 1) {
-    return rv.errata();
-  }
+  } else if constexpr (IDX == 1)
+  { return rv.errata(); }
 }
 
 } // namespace swoc
diff --git a/swoc++/include/swoc/IntrusiveDList.h b/swoc++/include/swoc/IntrusiveDList.h
index f974b4b..4d69aa9 100644
--- a/swoc++/include/swoc/IntrusiveDList.h
+++ b/swoc++/include/swoc/IntrusiveDList.h
@@ -80,8 +80,7 @@ namespace swoc
     between item pointers and iterators.
 
   */
-template <typename L> class IntrusiveDList
-{
+template <typename L> class IntrusiveDList {
   friend class iterator;
 
 public:
@@ -94,8 +93,7 @@ public:
     typename std::remove_reference<typename std::invoke_result<decltype(L::next_ptr), nullptr_t>::type>::type>::type;
 
   /// Const iterator.
-  class const_iterator
-  {
+  class const_iterator {
     using self_type = const_iterator; ///< Self reference type.
     friend class IntrusiveDList;
 
@@ -161,8 +159,7 @@ public:
   };
 
   /// Iterator for the list.
-  class iterator : public const_iterator
-  {
+  class iterator : public const_iterator {
     using self_type  = iterator;       ///< Self reference type.
     using super_type = const_iterator; ///< Super class type.
 
@@ -364,14 +361,12 @@ template <typename T, T *(T::*NEXT) = &T::_next, T *(T::*PREV) = &T::_prev> stru
 
 template <typename T, T *(T::*NEXT), T *(T::*PREV)>
 T *&
-IntrusiveLinkage<T, NEXT, PREV>::next_ptr(T *thing)
-{
+IntrusiveLinkage<T, NEXT, PREV>::next_ptr(T *thing) {
   return thing->*NEXT;
 }
 template <typename T, T *(T::*NEXT), T *(T::*PREV)>
 T *&
-IntrusiveLinkage<T, NEXT, PREV>::prev_ptr(T *thing)
-{
+IntrusiveLinkage<T, NEXT, PREV>::prev_ptr(T *thing) {
   return thing->*PREV;
 }
 
@@ -403,8 +398,7 @@ IntrusiveLinkage<T, NEXT, PREV>::prev_ptr(T *thing)
  */
 template <typename T, typename P>
 T *&
-ptr_ref_cast(P *&p)
-{
+ptr_ref_cast(P *&p) {
   union {
     P **_p;
     T **_t;
@@ -439,15 +433,13 @@ template <typename T, typename L> struct IntrusiveLinkageRebind {
 
 template <typename T, typename L>
 T *&
-IntrusiveLinkageRebind<T, L>::next_ptr(T *thing)
-{
+IntrusiveLinkageRebind<T, L>::next_ptr(T *thing) {
   return ptr_ref_cast<T>(L::next_ptr(thing));
 }
 
 template <typename T, typename L>
 T *&
-IntrusiveLinkageRebind<T, L>::prev_ptr(T *thing)
-{
+IntrusiveLinkageRebind<T, L>::prev_ptr(T *thing) {
   return ptr_ref_cast<T>(L::prev_ptr(thing));
 }
 
@@ -457,9 +449,7 @@ template <typename L> IntrusiveDList<L>::const_iterator::const_iterator() {}
 
 template <typename L>
 IntrusiveDList<L>::const_iterator::const_iterator(const list_type *list, value_type *v)
-  : _list(const_cast<list_type *>(list)), _v(const_cast<typename list_type::value_type *>(v))
-{
-}
+  : _list(const_cast<list_type *>(list)), _v(const_cast<typename list_type::value_type *>(v)) {}
 
 template <typename L> IntrusiveDList<L>::iterator::iterator() {}
 
@@ -467,24 +457,21 @@ template <typename L> IntrusiveDList<L>::iterator::iterator(list_type *list, val
 
 template <typename L>
 auto
-IntrusiveDList<L>::const_iterator::operator++() -> self_type &
-{
+IntrusiveDList<L>::const_iterator::operator++() -> self_type & {
   _v = L::next_ptr(_v);
   return *this;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::iterator::operator++() -> self_type &
-{
+IntrusiveDList<L>::iterator::operator++() -> self_type & {
   this->super_type::operator++();
   return *this;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::const_iterator::operator++(int) -> self_type
-{
+IntrusiveDList<L>::const_iterator::operator++(int) -> self_type {
   self_type tmp(*this);
   ++*this;
   return tmp;
@@ -492,8 +479,7 @@ IntrusiveDList<L>::const_iterator::operator++(int) -> self_type
 
 template <typename L>
 auto
-IntrusiveDList<L>::iterator::operator++(int) -> self_type
-{
+IntrusiveDList<L>::iterator::operator++(int) -> self_type {
   self_type tmp(*this);
   ++*this;
   return tmp;
@@ -501,28 +487,25 @@ IntrusiveDList<L>::iterator::operator++(int) -> self_type
 
 template <typename L>
 auto
-IntrusiveDList<L>::const_iterator::operator--() -> self_type &
-{
-  if (_v) {
+IntrusiveDList<L>::const_iterator::operator--() -> self_type & {
+  if (_v)
+  {
     _v = L::prev_ptr(_v);
-  } else if (_list) {
-    _v = _list->_tail;
-  }
+  } else if (_list)
+  { _v = _list->_tail; }
   return *this;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::iterator::operator--() -> self_type &
-{
+IntrusiveDList<L>::iterator::operator--() -> self_type & {
   this->super_type::operator--();
   return *this;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::const_iterator::operator--(int) -> self_type
-{
+IntrusiveDList<L>::const_iterator::operator--(int) -> self_type {
   self_type tmp(*this);
   --*this;
   return tmp;
@@ -530,63 +513,54 @@ IntrusiveDList<L>::const_iterator::operator--(int) -> self_type
 
 template <typename L>
 auto
-IntrusiveDList<L>::iterator::operator--(int) -> self_type
-{
+IntrusiveDList<L>::iterator::operator--(int) -> self_type {
   self_type tmp(*this);
   --*this;
   return tmp;
 }
 
-template <typename L> auto IntrusiveDList<L>::const_iterator::operator-> () const -> value_type *
-{
+template <typename L> auto IntrusiveDList<L>::const_iterator::operator-> () const -> value_type * {
   return _v;
 }
 
-template <typename L> auto IntrusiveDList<L>::iterator::operator-> () const -> value_type *
-{
+template <typename L> auto IntrusiveDList<L>::iterator::operator-> () const -> value_type * {
   return super_type::_v;
 }
 
-template <typename L> IntrusiveDList<L>::const_iterator::operator value_type *() const
-{
+template <typename L> IntrusiveDList<L>::const_iterator::operator value_type *() const {
   return _v;
 }
 
-template <typename L> auto IntrusiveDList<L>::const_iterator::operator*() const -> value_type &
-{
+template <typename L> auto IntrusiveDList<L>::const_iterator::operator*() const -> value_type & {
   return *_v;
 }
 
-template <typename L> auto IntrusiveDList<L>::iterator::operator*() const -> value_type &
-{
+template <typename L> auto IntrusiveDList<L>::iterator::operator*() const -> value_type & {
   return *super_type::_v;
 }
 
-template <typename L> IntrusiveDList<L>::iterator::operator value_type *() const
-{
+template <typename L> IntrusiveDList<L>::iterator::operator value_type *() const {
   return super_type::_v;
 }
 
 /// --- Main class
 
 template <typename L>
-IntrusiveDList<L>::IntrusiveDList(self_type &&that) : _head(that._head), _tail(that._tail), _count(that._count)
-{
+IntrusiveDList<L>::IntrusiveDList(self_type &&that) : _head(that._head), _tail(that._tail), _count(that._count) {
   that.clear();
 }
 
 template <typename L>
 bool
-IntrusiveDList<L>::empty() const
-{
+IntrusiveDList<L>::empty() const {
   return _head == nullptr;
 }
 
 template <typename L>
 bool
-IntrusiveDList<L>::contains(const value_type *v) const
-{
-  for (auto thing = _head; thing; thing = L::next_ptr(thing)) {
+IntrusiveDList<L>::contains(const value_type *v) const {
+  for (auto thing = _head; thing; thing = L::next_ptr(thing))
+  {
     if (thing == v)
       return true;
   }
@@ -595,26 +569,25 @@ IntrusiveDList<L>::contains(const value_type *v) const
 
 template <typename L>
 bool
-IntrusiveDList<L>::const_iterator::operator==(self_type const &that) const
-{
+IntrusiveDList<L>::const_iterator::operator==(self_type const &that) const {
   return this->_v == that._v;
 }
 
 template <typename L>
 bool
-IntrusiveDList<L>::const_iterator::operator!=(self_type const &that) const
-{
+IntrusiveDList<L>::const_iterator::operator!=(self_type const &that) const {
   return this->_v != that._v;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::prepend(value_type *v) -> self_type &
-{
+IntrusiveDList<L>::prepend(value_type *v) -> self_type & {
   L::prev_ptr(v) = nullptr;
-  if (nullptr != (L::next_ptr(v) = _head)) {
+  if (nullptr != (L::next_ptr(v) = _head))
+  {
     L::prev_ptr(_head) = v;
-  } else {
+  } else
+  {
     _tail = v; // transition empty -> non-empty
   }
   _head = v;
@@ -624,12 +597,13 @@ IntrusiveDList<L>::prepend(value_type *v) -> self_type &
 
 template <typename L>
 auto
-IntrusiveDList<L>::append(value_type *v) -> self_type &
-{
+IntrusiveDList<L>::append(value_type *v) -> self_type & {
   L::next_ptr(v) = nullptr;
-  if (nullptr != (L::prev_ptr(v) = _tail)) {
+  if (nullptr != (L::prev_ptr(v) = _tail))
+  {
     L::next_ptr(_tail) = v;
-  } else {
+  } else
+  {
     _head = v; // transition empty -> non-empty
   }
   _tail = v;
@@ -639,15 +613,15 @@ IntrusiveDList<L>::append(value_type *v) -> self_type &
 
 template <typename L>
 auto
-IntrusiveDList<L>::take_head() -> value_type *
-{
+IntrusiveDList<L>::take_head() -> value_type * {
   value_type *zret = _head;
-  if (_head) {
-    if (nullptr == (_head = L::next_ptr(_head))) {
+  if (_head)
+  {
+    if (nullptr == (_head = L::next_ptr(_head)))
+    {
       _tail = nullptr; // transition non-empty -> empty
-    } else {
-      L::prev_ptr(_head) = nullptr;
-    }
+    } else
+    { L::prev_ptr(_head) = nullptr; }
     L::next_ptr(zret) = L::prev_ptr(zret) = nullptr;
     --_count;
   }
@@ -656,15 +630,15 @@ IntrusiveDList<L>::take_head() -> value_type *
 
 template <typename L>
 auto
-IntrusiveDList<L>::take_tail() -> value_type *
-{
+IntrusiveDList<L>::take_tail() -> value_type * {
   value_type *zret = _tail;
-  if (_tail) {
-    if (nullptr == (_tail = L::prev_ptr(_tail))) {
+  if (_tail)
+  {
+    if (nullptr == (_tail = L::prev_ptr(_tail)))
+    {
       _head = nullptr; // transition non-empty -> empty
-    } else {
-      L::next_ptr(_tail) = nullptr;
-    }
+    } else
+    { L::next_ptr(_tail) = nullptr; }
     L::next_ptr(zret) = L::prev_ptr(zret) = nullptr;
     --_count;
   }
@@ -673,75 +647,74 @@ IntrusiveDList<L>::take_tail() -> value_type *
 
 template <typename L>
 auto
-IntrusiveDList<L>::insert_after(value_type *target, value_type *v) -> self_type &
-{
-  if (target) {
-    if (nullptr != (L::next_ptr(v) = L::next_ptr(target))) {
+IntrusiveDList<L>::insert_after(value_type *target, value_type *v) -> self_type & {
+  if (target)
+  {
+    if (nullptr != (L::next_ptr(v) = L::next_ptr(target)))
+    {
       L::prev_ptr(L::next_ptr(v)) = v;
-    } else if (_tail == target) {
-      _tail = v;
-    }
+    } else if (_tail == target)
+    { _tail = v; }
     L::prev_ptr(v)      = target;
     L::next_ptr(target) = v;
 
     ++_count;
-  } else {
-    this->append(v);
-  }
+  } else
+  { this->append(v); }
   return *this;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::insert_after(iterator const &target, value_type *v) -> self_type &
-{
+IntrusiveDList<L>::insert_after(iterator const &target, value_type *v) -> self_type & {
   return this->insert_after(target._v, v);
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::insert_before(value_type *target, value_type *v) -> self_type &
-{
-  if (target) {
-    if (nullptr != (L::prev_ptr(v) = L::prev_ptr(target))) {
+IntrusiveDList<L>::insert_before(value_type *target, value_type *v) -> self_type & {
+  if (target)
+  {
+    if (nullptr != (L::prev_ptr(v) = L::prev_ptr(target)))
+    {
       L::next_ptr(L::prev_ptr(v)) = v;
-    } else if (target == _head) {
-      _head = v;
-    }
+    } else if (target == _head)
+    { _head = v; }
     L::next_ptr(v)      = target;
     L::prev_ptr(target) = v;
 
     ++_count;
-  } else {
-    this->append(v);
-  }
+  } else
+  { this->append(v); }
   return *this;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::insert_before(iterator const &target, value_type *v) -> self_type &
-{
+IntrusiveDList<L>::insert_before(iterator const &target, value_type *v) -> self_type & {
   return this->insert_before(target._v, v);
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::erase(value_type *v) -> value_type *
-{
+IntrusiveDList<L>::erase(value_type *v) -> value_type * {
   value_type *zret{nullptr};
 
-  if (L::prev_ptr(v)) {
+  if (L::prev_ptr(v))
+  {
     L::next_ptr(L::prev_ptr(v)) = L::next_ptr(v);
   }
-  if (L::next_ptr(v)) {
+  if (L::next_ptr(v))
+  {
     zret                        = L::next_ptr(v);
     L::prev_ptr(L::next_ptr(v)) = L::prev_ptr(v);
   }
-  if (v == _head) {
+  if (v == _head)
+  {
     _head = L::next_ptr(v);
   }
-  if (v == _tail) {
+  if (v == _tail)
+  {
     _tail = L::prev_ptr(v);
   }
   L::prev_ptr(v) = L::next_ptr(v) = nullptr;
@@ -752,31 +725,32 @@ IntrusiveDList<L>::erase(value_type *v) -> value_type *
 
 template <typename L>
 auto
-IntrusiveDList<L>::erase(const iterator &loc) -> iterator
-{
+IntrusiveDList<L>::erase(const iterator &loc) -> iterator {
   return this->iterator_for(this->erase(loc._v));
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::erase(const iterator &first, const iterator &limit) -> iterator
-{
+IntrusiveDList<L>::erase(const iterator &first, const iterator &limit) -> iterator {
   value_type *spot = first;
   value_type *prev{L::prev_ptr(spot)};
-  if (prev) {
+  if (prev)
+  {
     L::next_ptr(prev) = limit;
   }
-  if (spot == _head) {
+  if (spot == _head)
+  {
     _head = limit;
   }
   // tail is only updated if @a limit is @a end (e.g., @c nullptr).
-  if (nullptr == limit) {
+  if (nullptr == limit)
+  {
     _tail = prev;
-  } else {
-    L::prev_ptr(limit) = prev;
-  }
+  } else
+  { L::prev_ptr(limit) = prev; }
   // Clear links in removed elements.
-  while (spot != limit) {
+  while (spot != limit)
+  {
     value_type *target{spot};
     spot                = L::next_ptr(spot);
     L::prev_ptr(target) = L::next_ptr(target) = nullptr;
@@ -787,9 +761,9 @@ IntrusiveDList<L>::erase(const iterator &first, const iterator &limit) -> iterat
 
 template <typename L>
 auto
-IntrusiveDList<L>::operator=(self_type &&that) -> self_type &
-{
-  if (this != &that) {
+IntrusiveDList<L>::operator=(self_type &&that) -> self_type & {
+  if (this != &that)
+  {
     this->_head  = that._head;
     this->_tail  = that._tail;
     this->_count = that._count;
@@ -800,85 +774,73 @@ IntrusiveDList<L>::operator=(self_type &&that) -> self_type &
 
 template <typename L>
 size_t
-IntrusiveDList<L>::count() const
-{
+IntrusiveDList<L>::count() const {
   return _count;
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::begin() const -> const_iterator
-{
+IntrusiveDList<L>::begin() const -> const_iterator {
   return const_iterator{this, _head};
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::begin() -> iterator
-{
+IntrusiveDList<L>::begin() -> iterator {
   return iterator{this, _head};
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::end() const -> const_iterator
-{
+IntrusiveDList<L>::end() const -> const_iterator {
   return const_iterator{this, nullptr};
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::end() -> iterator
-{
+IntrusiveDList<L>::end() -> iterator {
   return iterator{this, nullptr};
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::iterator_for(value_type *v) -> iterator
-{
+IntrusiveDList<L>::iterator_for(value_type *v) -> iterator {
   return iterator{this, v};
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::iterator_for(const value_type *v) const -> const_iterator
-{
+IntrusiveDList<L>::iterator_for(const value_type *v) const -> const_iterator {
   return const_iterator{this, v};
 };
 
 template <typename L>
 auto
-IntrusiveDList<L>::tail() -> value_type *
-{
+IntrusiveDList<L>::tail() -> value_type * {
   return _tail;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::tail() const -> const value_type *
-{
+IntrusiveDList<L>::tail() const -> const value_type * {
   return _tail;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::head() -> value_type *
-{
+IntrusiveDList<L>::head() -> value_type * {
   return _head;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::head() const -> const value_type *
-{
+IntrusiveDList<L>::head() const -> const value_type * {
   return _head;
 }
 
 template <typename L>
 auto
-IntrusiveDList<L>::clear() -> self_type &
-{
+IntrusiveDList<L>::clear() -> self_type & {
   _head = _tail = nullptr;
   _count        = 0;
   return *this;
@@ -896,19 +858,18 @@ namespace detail
   template <typename L, typename F>
   auto
   Intrusive_DList_Apply(IntrusiveDList<L> &list, F &&f)
-    -> decltype(f(*static_cast<typename IntrusiveDList<L>::value_type *>(nullptr)), list)
-  {
+    -> decltype(f(*static_cast<typename IntrusiveDList<L>::value_type *>(nullptr)), list) {
     return list.apply([&f](typename IntrusiveDList<L>::value_type *v) { return f(*v); });
   }
 
   template <typename L, typename F>
   auto
   Intrusive_DList_Apply(IntrusiveDList<L> &list, F &&f)
-    -> decltype(f(static_cast<typename IntrusiveDList<L>::value_type *>(nullptr)), list)
-  {
+    -> decltype(f(static_cast<typename IntrusiveDList<L>::value_type *>(nullptr)), list) {
     auto spot{list.begin()};
     auto limit{list.end()};
-    while (spot != limit) {
+    while (spot != limit)
+    {
       f(spot++); // post increment means @a spot is updated before @a f is applied.
     }
     return list;
@@ -918,8 +879,7 @@ namespace detail
 template <typename L>
 template <typename F>
 auto
-IntrusiveDList<L>::apply(F &&f) -> self_type &
-{
+IntrusiveDList<L>::apply(F &&f) -> self_type & {
   return detail::Intrusive_DList_Apply(*this, f);
 };
 
diff --git a/swoc++/include/swoc/IntrusiveHashMap.h b/swoc++/include/swoc/IntrusiveHashMap.h
index b7cf5d3..7a3204d 100644
--- a/swoc++/include/swoc/IntrusiveHashMap.h
+++ b/swoc++/include/swoc/IntrusiveHashMap.h
@@ -80,8 +80,7 @@ namespace swoc
     @endcode
 
  */
-template <typename H> class IntrusiveHashMap
-{
+template <typename H> class IntrusiveHashMap {
   using self_type = IntrusiveHashMap;
 
 public:
@@ -313,15 +312,13 @@ protected:
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::Bucket::Linkage::next_ptr(Bucket *b) -> Bucket *&
-{
+IntrusiveHashMap<H>::Bucket::Linkage::next_ptr(Bucket *b) -> Bucket *& {
   return b->_link._next;
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::Bucket::Linkage::prev_ptr(Bucket *b) -> Bucket *&
-{
+IntrusiveHashMap<H>::Bucket::Linkage::prev_ptr(Bucket *b) -> Bucket *& {
   return b->_link._prev;
 }
 
@@ -329,16 +326,14 @@ IntrusiveHashMap<H>::Bucket::Linkage::prev_ptr(Bucket *b) -> Bucket *&
 // a search loop on an empty bucket because that will start with a nullptr candidate, matching the limit.
 template <typename H>
 auto
-IntrusiveHashMap<H>::Bucket::limit() const -> value_type *
-{
+IntrusiveHashMap<H>::Bucket::limit() const -> value_type * {
   Bucket *n{_link._next};
   return n ? n->_v : nullptr;
 };
 
 template <typename H>
 void
-IntrusiveHashMap<H>::Bucket::clear()
-{
+IntrusiveHashMap<H>::Bucket::clear() {
   _v       = nullptr;
   _count   = 0;
   _mixed_p = false;
@@ -348,11 +343,11 @@ IntrusiveHashMap<H>::Bucket::clear()
 
 template <typename H>
 bool
-IntrusiveHashMap<H>::Bucket::contains(value_type *v) const
-{
+IntrusiveHashMap<H>::Bucket::contains(value_type *v) const {
   value_type *x     = _v;
   value_type *limit = this->limit();
-  while (x != limit && x != v) {
+  while (x != limit && x != v)
+  {
     x = H::next_ptr(x);
   }
   return x == v;
@@ -361,80 +356,71 @@ IntrusiveHashMap<H>::Bucket::contains(value_type *v) const
 // ---------------------
 template <typename H>
 auto
-IntrusiveHashMap<H>::range::begin() const -> iterator const &
-{
+IntrusiveHashMap<H>::range::begin() const -> iterator const & {
   return super_type::first;
 }
 template <typename H>
 auto
-IntrusiveHashMap<H>::range::end() const -> iterator const &
-{
+IntrusiveHashMap<H>::range::end() const -> iterator const & {
   return super_type::second;
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::const_range::begin() const -> const_iterator const &
-{
+IntrusiveHashMap<H>::const_range::begin() const -> const_iterator const & {
   return super_type::first;
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::const_range::end() const -> const_iterator const &
-{
+IntrusiveHashMap<H>::const_range::end() const -> const_iterator const & {
   return super_type::second;
 }
 
 // ---------------------
 
-template <typename H> IntrusiveHashMap<H>::IntrusiveHashMap(size_t n)
-{
-  if (n) {
+template <typename H> IntrusiveHashMap<H>::IntrusiveHashMap(size_t n) {
+  if (n)
+  {
     _table.resize(*std::lower_bound(PRIME.begin(), PRIME.end(), n));
   }
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::bucket_for(key_type key) -> Bucket *
-{
+IntrusiveHashMap<H>::bucket_for(key_type key) -> Bucket * {
   return &_table[H::hash_of(key) % _table.size()];
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::begin() -> iterator
-{
+IntrusiveHashMap<H>::begin() -> iterator {
   return _list.begin();
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::begin() const -> const_iterator
-{
+IntrusiveHashMap<H>::begin() const -> const_iterator {
   return _list.begin();
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::end() -> iterator
-{
+IntrusiveHashMap<H>::end() -> iterator {
   return _list.end();
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::end() const -> const_iterator
-{
+IntrusiveHashMap<H>::end() const -> const_iterator {
   return _list.end();
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::clear() -> self_type &
-{
-  for (auto &b : _table) {
+IntrusiveHashMap<H>::clear() -> self_type & {
+  for (auto &b : _table)
+  {
     b.clear();
   }
   // Clear container data.
@@ -445,12 +431,12 @@ IntrusiveHashMap<H>::clear() -> self_type &
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::find(key_type key) -> iterator
-{
+IntrusiveHashMap<H>::find(key_type key) -> iterator {
   Bucket *b         = this->bucket_for(key);
   value_type *v     = b->_v;
   value_type *limit = b->limit();
-  while (v != limit && !H::equal(key, H::key_of(v))) {
+  while (v != limit && !H::equal(key, H::key_of(v)))
+  {
     v = H::next_ptr(v);
   }
   return v == limit ? _list.end() : _list.iterator_for(v);
@@ -458,20 +444,19 @@ IntrusiveHashMap<H>::find(key_type key) -> iterator
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::find(key_type key) const -> const_iterator
-{
+IntrusiveHashMap<H>::find(key_type key) const -> const_iterator {
   return const_cast<self_type *>(this)->find(key);
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::equal_range(key_type key) -> range
-{
+IntrusiveHashMap<H>::equal_range(key_type key) -> range {
   iterator first{this->find(key)};
   iterator last{first};
   iterator limit{this->end()};
 
-  while (last != limit && H::equal(key, H::key_of(&*last))) {
+  while (last != limit && H::equal(key, H::key_of(&*last)))
+  {
     ++last;
   }
 
@@ -480,75 +465,75 @@ IntrusiveHashMap<H>::equal_range(key_type key) -> range
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::equal_range(key_type key) const -> const_range
-{
+IntrusiveHashMap<H>::equal_range(key_type key) const -> const_range {
   return const_cast<self_type *>(this)->equal_range(key);
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::iterator_for(const value_type *v) const -> const_iterator
-{
+IntrusiveHashMap<H>::iterator_for(const value_type *v) const -> const_iterator {
   return _list.iterator_for(v);
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::iterator_for(value_type *v) -> iterator
-{
+IntrusiveHashMap<H>::iterator_for(value_type *v) -> iterator {
   return _list.iterator_for(v);
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::find(value_type *v) -> iterator
-{
+IntrusiveHashMap<H>::find(value_type *v) -> iterator {
   Bucket *b = this->bucket_for(H::key_of(v));
   return b->contains(v) ? _list.iterator_for(v) : this->end();
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::find(value_type const *v) const -> const_iterator
-{
+IntrusiveHashMap<H>::find(value_type const *v) const -> const_iterator {
   return const_cast<self_type *>(this)->find(const_cast<value_type *>(v));
 }
 
 template <typename H>
 void
-IntrusiveHashMap<H>::insert(value_type *v)
-{
+IntrusiveHashMap<H>::insert(value_type *v) {
   auto key         = H::key_of(v);
   Bucket *bucket   = this->bucket_for(key);
   value_type *spot = bucket->_v;
   bool mixed_p     = false; // Found a different key in the bucket.
 
-  if (nullptr == spot) { // currently empty bucket, set it and add to active list.
+  if (nullptr == spot)
+  { // currently empty bucket, set it and add to active list.
     _list.append(v);
     bucket->_v = v;
     _active_buckets.append(bucket);
-  } else {
+  } else
+  {
     value_type *limit = bucket->limit();
 
     // First search the bucket to see if the key is already in it.
-    while (spot != limit && !H::equal(key, H::key_of(spot))) {
+    while (spot != limit && !H::equal(key, H::key_of(spot)))
+    {
       spot = H::next_ptr(spot);
     }
-    if (spot != bucket->_v) {
+    if (spot != bucket->_v)
+    {
       mixed_p = true; // found some other key, it's going to be mixed.
     }
-    if (spot != limit) {
+    if (spot != limit)
+    {
       // If an equal key was found, walk past those to insert at the upper end of the range.
-      do {
-        spot = H::next_ptr(spot);
-      } while (spot != limit && H::equal(key, H::key_of(spot)));
-      if (spot != limit) { // something not equal past last equivalent, it's going to be mixed.
+      do
+      { spot = H::next_ptr(spot); } while (spot != limit && H::equal(key, H::key_of(spot)));
+      if (spot != limit)
+      { // something not equal past last equivalent, it's going to be mixed.
         mixed_p = true;
       }
     }
 
     _list.insert_before(spot, v);
-    if (spot == bucket->_v) { // added before the bucket start, update the start.
+    if (spot == bucket->_v)
+    { // added before the bucket start, update the start.
       bucket->_v = v;
     }
     bucket->_mixed_p = mixed_p;
@@ -557,25 +542,28 @@ IntrusiveHashMap<H>::insert(value_type *v)
 
   // auto expand if appropriate.
   if ((AVERAGE == _expansion_policy && (_list.count() / _table.size()) > _expansion_limit) ||
-      (MAXIMUM == _expansion_policy && bucket->_count > _expansion_limit && bucket->_mixed_p)) {
+      (MAXIMUM == _expansion_policy && bucket->_count > _expansion_limit && bucket->_mixed_p))
+  {
     this->expand();
   }
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::erase(iterator const &loc) -> iterator
-{
+IntrusiveHashMap<H>::erase(iterator const &loc) -> iterator {
   value_type *v     = loc;
   iterator zret     = ++(this->iterator_for(v)); // get around no const_iterator -> iterator.
   Bucket *b         = this->bucket_for(H::key_of(v));
   value_type *nv    = H::next_ptr(v);
   value_type *limit = b->limit();
-  if (b->_v == v) {    // removed first element in bucket, update bucket
-    if (limit == nv) { // that was also the only element, deactivate bucket
+  if (b->_v == v)
+  { // removed first element in bucket, update bucket
+    if (limit == nv)
+    { // that was also the only element, deactivate bucket
       _active_buckets.erase(b);
       b->clear();
-    } else {
+    } else
+    {
       b->_v = nv;
       --b->_count;
     }
@@ -586,10 +574,10 @@ IntrusiveHashMap<H>::erase(iterator const &loc) -> iterator
 
 template <typename H>
 bool
-IntrusiveHashMap<H>::erase(value_type *value)
-{
+IntrusiveHashMap<H>::erase(value_type *value) {
   auto loc = this->find(value);
-  if (loc != this->end()) {
+  if (loc != this->end())
+  {
     this->erase(loc);
     return true;
   }
@@ -598,19 +586,21 @@ IntrusiveHashMap<H>::erase(value_type *value)
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::erase(iterator const &start, iterator const &limit) -> iterator
-{
+IntrusiveHashMap<H>::erase(iterator const &start, iterator const &limit) -> iterator {
   auto spot{start};
   Bucket *bucket{this->bucket_for(spot)};
-  while (spot != limit) {
+  while (spot != limit)
+  {
     auto target         = bucket;
     bucket              = bucket->_link._next; // bump now to avoid forward iteration problems in case of bucket removal.
     value_type *v_limit = bucket ? bucket->_v : nullptr;
-    while (spot != v_limit && spot != limit) {
+    while (spot != v_limit && spot != limit)
+    {
       --(target->_count);
       ++spot;
     }
-    if (target->_count == 0) {
+    if (target->_count == 0)
+    {
       _active_buckets.erase(target);
     }
   }
@@ -620,8 +610,7 @@ IntrusiveHashMap<H>::erase(iterator const &start, iterator const &limit) -> iter
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::erase(range const &r) -> iterator
-{
+IntrusiveHashMap<H>::erase(range const &r) -> iterator {
   return this->erase(r.first, r.second);
 }
 
@@ -636,19 +625,18 @@ namespace detail
   template <typename H, typename F>
   auto
   IntrusiveHashMapApply(IntrusiveHashMap<H> &map, F &&f)
-    -> decltype(f(*static_cast<typename IntrusiveHashMap<H>::value_type *>(nullptr)), map)
-  {
+    -> decltype(f(*static_cast<typename IntrusiveHashMap<H>::value_type *>(nullptr)), map) {
     return map.apply([&f](typename IntrusiveHashMap<H>::value_type *v) { return f(*v); });
   }
 
   template <typename H, typename F>
   auto
   IntrusiveHashMapApply(IntrusiveHashMap<H> &map, F &&f)
-    -> decltype(f(static_cast<typename IntrusiveHashMap<H>::value_type *>(nullptr)), map)
-  {
+    -> decltype(f(static_cast<typename IntrusiveHashMap<H>::value_type *>(nullptr)), map) {
     auto spot{map.begin()};
     auto limit{map.end()};
-    while (spot != limit) {
+    while (spot != limit)
+    {
       f(spot++); // post increment means @a spot is updated before @a f is applied.
     }
     return map;
@@ -658,15 +646,13 @@ namespace detail
 template <typename H>
 template <typename F>
 auto
-IntrusiveHashMap<H>::apply(F &&f) -> self_type &
-{
+IntrusiveHashMap<H>::apply(F &&f) -> self_type & {
   return detail::IntrusiveHashMapApply(*this, f);
 };
 
 template <typename H>
 void
-IntrusiveHashMap<H>::expand()
-{
+IntrusiveHashMap<H>::expand() {
   ExpansionPolicy org_expansion_policy = _expansion_policy; // save for restore.
   value_type *old                      = _list.head();      // save for repopulating.
   auto old_size                        = _table.size();
@@ -676,7 +662,8 @@ IntrusiveHashMap<H>::expand()
   _table.resize(*std::lower_bound(PRIME.begin(), PRIME.end(), old_size + 1));
 
   _expansion_policy = MANUAL; // disable any auto expand while we're expanding.
-  while (old) {
+  while (old)
+  {
     value_type *v = old;
     old           = H::next_ptr(old);
     this->insert(v);
@@ -687,45 +674,39 @@ IntrusiveHashMap<H>::expand()
 
 template <typename H>
 size_t
-IntrusiveHashMap<H>::count() const
-{
+IntrusiveHashMap<H>::count() const {
   return _list.count();
 }
 
 template <typename H>
 size_t
-IntrusiveHashMap<H>::bucket_count() const
-{
+IntrusiveHashMap<H>::bucket_count() const {
   return _table.size();
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::set_expansion_policy(ExpansionPolicy policy) -> self_type &
-{
+IntrusiveHashMap<H>::set_expansion_policy(ExpansionPolicy policy) -> self_type & {
   _expansion_policy = policy;
   return *this;
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::get_expansion_policy() const -> ExpansionPolicy
-{
+IntrusiveHashMap<H>::get_expansion_policy() const -> ExpansionPolicy {
   return _expansion_policy;
 }
 
 template <typename H>
 auto
-IntrusiveHashMap<H>::set_expansion_limit(size_t n) -> self_type &
-{
+IntrusiveHashMap<H>::set_expansion_limit(size_t n) -> self_type & {
   _expansion_limit = n;
   return *this;
 }
 
 template <typename H>
 size_t
-IntrusiveHashMap<H>::get_expansion_limit() const
-{
+IntrusiveHashMap<H>::get_expansion_limit() const {
   return _expansion_limit;
 }
 
diff --git a/swoc++/include/swoc/Lexicon.h b/swoc++/include/swoc/Lexicon.h
index b66360e..cb782f5 100644
--- a/swoc++/include/swoc/Lexicon.h
+++ b/swoc++/include/swoc/Lexicon.h
@@ -45,8 +45,7 @@ namespace detail
    */
   template <typename... Args>
   std::string
-  what(std::string_view const &fmt, Args &&... args)
-  {
+  what(std::string_view const &fmt, Args &&... args) {
     std::string zret;
     swoc::bwprint_v(zret, fmt, std::forward_as_tuple(args...));
     return std::move(zret);
@@ -75,8 +74,7 @@ namespace detail
     @note All names and value must be unique across the Lexicon. All name comparisons are case
     insensitive.
  */
-template <typename E> class Lexicon
-{
+template <typename E> class Lexicon {
   using self_type = Lexicon; ///< Self reference type.
 
 protected:
@@ -221,8 +219,7 @@ public:
   size_t count() const;
 
   /// Iterator over pairs of values and primary name pairs.
-  class const_iterator
-  {
+  class const_iterator {
     using self_type = const_iterator;
 
   public:
@@ -275,20 +272,17 @@ protected:
     E _value;
 
     std::string_view
-    operator()(std::monostate const &) const
-    {
+    operator()(std::monostate const &) const {
       throw std::domain_error(detail::what("Lexicon: invalid enumeration value {}", static_cast<int>(_value)).data());
     }
 
     std::string_view
-    operator()(std::string_view const &name) const
-    {
+    operator()(std::string_view const &name) const {
       return name;
     }
 
     std::string_view
-    operator()(UnknownValueHandler const &handler) const
-    {
+    operator()(UnknownValueHandler const &handler) const {
       return handler(_value);
     }
   };
@@ -298,20 +292,17 @@ protected:
     std::string_view _name;
 
     E
-    operator()(std::monostate const &) const
-    {
+    operator()(std::monostate const &) const {
       throw std::domain_error(detail::what("Lexicon: Unknown name \"{}\"", _name).data());
     }
 
     E
-    operator()(E const &value) const
-    {
+    operator()(E const &value) const {
       return value;
     }
 
     E
-    operator()(UnknownNameHandler const &handler) const
-    {
+    operator()(UnknownNameHandler const &handler) const {
       return handler(_name);
     }
   };
@@ -386,72 +377,62 @@ template <typename E> Lexicon<E>::Item::Item(E value, std::string_view name) : _
 
 template <typename E>
 auto
-Lexicon<E>::Item::NameLinkage::next_ptr(Item *item) -> Item *&
-{
+Lexicon<E>::Item::NameLinkage::next_ptr(Item *item) -> Item *& {
   return item->_name_link._next;
 }
 
 template <typename E>
 auto
-Lexicon<E>::Item::NameLinkage::prev_ptr(Item *item) -> Item *&
-{
+Lexicon<E>::Item::NameLinkage::prev_ptr(Item *item) -> Item *& {
   return item->_name_link._prev;
 }
 
 template <typename E>
 auto
-Lexicon<E>::Item::ValueLinkage::next_ptr(Item *item) -> Item *&
-{
+Lexicon<E>::Item::ValueLinkage::next_ptr(Item *item) -> Item *& {
   return item->_value_link._next;
 }
 
 template <typename E>
 auto
-Lexicon<E>::Item::ValueLinkage::prev_ptr(Item *item) -> Item *&
-{
+Lexicon<E>::Item::ValueLinkage::prev_ptr(Item *item) -> Item *& {
   return item->_value_link._prev;
 }
 
 template <typename E>
 std::string_view
-Lexicon<E>::Item::NameLinkage::key_of(Item *item)
-{
+Lexicon<E>::Item::NameLinkage::key_of(Item *item) {
   return item->_name;
 }
 
 template <typename E>
 E
-Lexicon<E>::Item::ValueLinkage::key_of(Item *item)
-{
+Lexicon<E>::Item::ValueLinkage::key_of(Item *item) {
   return item->_value;
 }
 
 template <typename E>
 uint32_t
-Lexicon<E>::Item::NameLinkage::hash_of(std::string_view s)
-{
+Lexicon<E>::Item::NameLinkage::hash_of(std::string_view s) {
   return Hash32FNV1a().hash_immediate(transform_view_of(&toupper, s));
 }
 
 template <typename E>
 uintmax_t
-Lexicon<E>::Item::ValueLinkage::hash_of(E value)
-{
+Lexicon<E>::Item::ValueLinkage::hash_of(E value) {
   // In almost all cases, the values will be (roughly) sequential, so an identity hash works well.
   return static_cast<uintmax_t>(value);
 }
 
 template <typename E>
 bool
-Lexicon<E>::Item::NameLinkage::equal(std::string_view const &lhs, std::string_view const &rhs)
-{
+Lexicon<E>::Item::NameLinkage::equal(std::string_view const &lhs, std::string_view const &rhs) {
   return 0 == strcasecmp(lhs, rhs);
 }
 
 template <typename E>
 bool
-Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs)
-{
+Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs) {
   return lhs == rhs;
 }
 
@@ -461,51 +442,52 @@ Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs)
 template <typename E> Lexicon<E>::Lexicon() {}
 
 template <typename E>
-Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items, DefaultHandler handler_1, DefaultHandler handler_2)
-{
-  for (auto const &item : items) {
+Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items, DefaultHandler handler_1, DefaultHandler handler_2) {
+  for (auto const &item : items)
+  {
     this->define(item.value, item.names);
   }
 
-  for (auto &&h : {handler_1, handler_2}) {
+  for (auto &&h : {handler_1, handler_2})
+  {
     this->set_default(h);
   }
 }
 
 template <typename E>
-Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items, DefaultHandler handler_1, DefaultHandler handler_2)
-{
-  for (auto const &item : items) {
+Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items, DefaultHandler handler_1, DefaultHandler handler_2) {
+  for (auto const &item : items)
+  {
     this->define(item);
   }
 
-  for (auto &&h : {handler_1, handler_2}) {
+  for (auto &&h : {handler_1, handler_2})
+  {
     this->set_default(h);
   }
 }
 
 template <typename E>
 std::string_view
-Lexicon<E>::localize(std::string_view const &name)
-{
+Lexicon<E>::localize(std::string_view const &name) {
   auto span = _arena.alloc(name.size());
   memcpy(span.data(), name.data(), name.size());
   return span.view();
 }
 
-template <typename E> std::string_view Lexicon<E>::operator[](E value) const
-{
+template <typename E> std::string_view Lexicon<E>::operator[](E value) const {
   auto spot = _by_value.find(value);
-  if (spot != _by_value.end()) {
+  if (spot != _by_value.end())
+  {
     return spot->_name;
   }
   return std::visit(NameDefaultVisitor{value}, _name_default);
 }
 
-template <typename E> E Lexicon<E>::operator[](std::string_view const &name) const
-{
+template <typename E> E Lexicon<E>::operator[](std::string_view const &name) const {
   auto spot = _by_name.find(name);
-  if (spot != _by_name.end()) {
+  if (spot != _by_name.end())
+  {
     return spot->_value;
   }
   return std::visit(ValueDefaultVisitor{name}, _value_default);
@@ -513,19 +495,22 @@ template <typename E> E Lexicon<E>::operator[](std::string_view const &name) con
 
 template <typename E>
 auto
-Lexicon<E>::define(E value, const std::initializer_list<std::string_view> &names) -> self_type &
-{
-  if (names.size() < 1) {
+Lexicon<E>::define(E value, const std::initializer_list<std::string_view> &names) -> self_type & {
+  if (names.size() < 1)
+  {
     throw std::invalid_argument("A defined value must have at least a primary name");
   }
-  for (auto name : names) {
-    if (_by_name.find(name) != _by_name.end()) {
+  for (auto name : names)
+  {
+    if (_by_name.find(name) != _by_name.end())
+    {
       throw std::invalid_argument(detail::what("Duplicate name '{}' in Lexicon", name));
     }
     auto i = new Item(value, this->localize(name));
     _by_name.insert(i);
     // Only put primary names in the value table.
-    if (_by_value.find(value) == _by_value.end()) {
+    if (_by_value.find(value) == _by_value.end())
+    {
       _by_value.insert(i);
     }
   }
@@ -535,31 +520,28 @@ Lexicon<E>::define(E value, const std::initializer_list<std::string_view> &names
 template <typename E>
 template <typename... Args>
 auto
-Lexicon<E>::define(E value, Args &&... names) -> self_type &
-{
+Lexicon<E>::define(E value, Args &&... names) -> self_type & {
   static_assert(sizeof...(Args) > 0, "A defined value must have at least a priamry name");
   return this->define(value, {std::forward<Args>(names)...});
 }
 
 template <typename E>
 auto
-Lexicon<E>::define(const Pair &pair) -> self_type &
-{
+Lexicon<E>::define(const Pair &pair) -> self_type & {
   return this->define(std::get<0>(pair), {std::get<1>(pair)});
 }
 
 template <typename E>
 auto
-Lexicon<E>::define(const Definition &init) -> self_type &
-{
+Lexicon<E>::define(const Definition &init) -> self_type & {
   return this->define(init.value, init.names);
 }
 
 template <typename E>
 auto
-Lexicon<E>::set_default(DefaultHandler const &handler) -> self_type &
-{
-  switch (handler.index()) {
+Lexicon<E>::set_default(DefaultHandler const &handler) -> self_type & {
+  switch (handler.index())
+  {
   case 0:
     break;
   case 1:
@@ -580,22 +562,19 @@ Lexicon<E>::set_default(DefaultHandler const &handler) -> self_type &
 
 template <typename E>
 size_t
-Lexicon<E>::count() const
-{
+Lexicon<E>::count() const {
   return _by_value.count();
 }
 
 template <typename E>
 auto
-Lexicon<E>::begin() const -> const_iterator
-{
+Lexicon<E>::begin() const -> const_iterator {
   return const_iterator{static_cast<const Item *>(_by_value.begin())};
 }
 
 template <typename E>
 auto
-Lexicon<E>::end() const -> const_iterator
-{
+Lexicon<E>::end() const -> const_iterator {
   return {};
 }
 
@@ -603,48 +582,43 @@ Lexicon<E>::end() const -> const_iterator
 
 template <typename E>
 void
-Lexicon<E>::const_iterator::update()
-{
+Lexicon<E>::const_iterator::update() {
   std::get<0>(_v) = _item->_value;
   std::get<1>(_v) = _item->_name;
 }
 
-template <typename E> Lexicon<E>::const_iterator::const_iterator(const Item *item) : _item(item)
-{
-  if (_item) {
+template <typename E> Lexicon<E>::const_iterator::const_iterator(const Item *item) : _item(item) {
+  if (_item)
+  {
     this->update();
   };
 }
 
-template <typename E> auto Lexicon<E>::const_iterator::operator*() const -> reference
-{
+template <typename E> auto Lexicon<E>::const_iterator::operator*() const -> reference {
   return _v;
 }
 
-template <typename E> auto Lexicon<E>::const_iterator::operator-> () const -> pointer
-{
+template <typename E> auto Lexicon<E>::const_iterator::operator-> () const -> pointer {
   return &_v;
 }
 
 template <typename E>
 bool
-Lexicon<E>::const_iterator::operator==(self_type const &that) const
-{
+Lexicon<E>::const_iterator::operator==(self_type const &that) const {
   return _item == that._item;
 }
 
 template <typename E>
 bool
-Lexicon<E>::const_iterator::operator!=(self_type const &that) const
-{
+Lexicon<E>::const_iterator::operator!=(self_type const &that) const {
   return _item != that._item;
 }
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator++() -> self_type &
-{
-  if (nullptr != (_item = _item->_value_link._next)) {
+Lexicon<E>::const_iterator::operator++() -> self_type & {
+  if (nullptr != (_item = _item->_value_link._next))
+  {
     this->update();
   }
   return *this;
@@ -652,8 +626,7 @@ Lexicon<E>::const_iterator::operator++() -> self_type &
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator++(int) -> self_type
-{
+Lexicon<E>::const_iterator::operator++(int) -> self_type {
   self_type tmp{*this};
   ++*this;
   return tmp;
@@ -661,9 +634,9 @@ Lexicon<E>::const_iterator::operator++(int) -> self_type
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator--() -> self_type &
-{
-  if (nullptr != (_item = _item->_value_link->_prev)) {
+Lexicon<E>::const_iterator::operator--() -> self_type & {
+  if (nullptr != (_item = _item->_value_link->_prev))
+  {
     this->update();
   }
   return *this;
@@ -671,8 +644,7 @@ Lexicon<E>::const_iterator::operator--() -> self_type &
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator--(int) -> self_type
-{
+Lexicon<E>::const_iterator::operator--(int) -> self_type {
   self_type tmp;
   ++*this;
   return tmp;
diff --git a/swoc++/include/swoc/MemArena.h b/swoc++/include/swoc/MemArena.h
index 2b2e3b0..3fb8caf 100644
--- a/swoc++/include/swoc/MemArena.h
+++ b/swoc++/include/swoc/MemArena.h
@@ -346,8 +346,8 @@ protected:
     Item *_next; ///< Next item in the free list.
   };
 
-  Item _list { nullptr }; ///< List of dead instances.
-  MemArena &_arena; ///< Memory source.
+  Item _list{nullptr}; ///< List of dead instances.
+  MemArena &_arena;    ///< Memory source.
 
 public:
   /** Construct a pool.
@@ -371,50 +371,46 @@ public:
    * The instance is destructed and then put on the free list for re-use.
    */
   void destroy(T *t);
+
+  /// Drop all items in the free list.
+  void clear();
 };
 // Implementation
 
-inline auto MemArena::Block::Linkage::next_ptr(Block *b) -> Block *&
-{
+inline auto MemArena::Block::Linkage::next_ptr(Block *b) -> Block *& {
   return b->_link._next;
 }
 
-inline auto MemArena::Block::Linkage::prev_ptr(Block *b) -> Block *&
-{
+inline auto MemArena::Block::Linkage::prev_ptr(Block *b) -> Block *& {
   return b->_link._prev;
 }
 
 inline MemArena::Block::Block(size_t n) : size(n) {}
 
-inline char *MemArena::Block::data()
-{
+inline char *MemArena::Block::data() {
   return reinterpret_cast<char *>(this + 1);
 }
 
-inline const char *MemArena::Block::data() const
-{
+inline const char *MemArena::Block::data() const {
   return reinterpret_cast<const char *>(this + 1);
 }
 
-inline bool MemArena::Block::contains(const void *ptr) const
-{
+inline bool MemArena::Block::contains(const void *ptr) const {
   const char *base = this->data();
   return base <= ptr && ptr < base + size;
 }
 
-inline size_t MemArena::Block::remaining() const
-{
+inline size_t MemArena::Block::remaining() const {
   return size - allocated;
 }
 
-inline bool MemArena::Block::is_full() const
-{
+inline bool MemArena::Block::is_full() const {
   return this->remaining() < MIN_FREE_SPACE;
 }
 
-inline MemSpan<void> MemArena::Block::alloc(size_t n)
-{
-  if (n > this->remaining()) {
+inline MemSpan<void> MemArena::Block::alloc(size_t n) {
+  if (n > this->remaining())
+  {
     throw(std::invalid_argument{"MemArena::Block::alloc size is more than remaining."});
   }
   MemSpan<void> zret = this->remnant().prefix(n);
@@ -422,85 +418,71 @@ inline MemSpan<void> MemArena::Block::alloc(size_t n)
   return zret;
 }
 
-template <typename T, typename... Args> T *MemArena::make(Args &&... args)
-{
+template <typename T, typename... Args> T *MemArena::make(Args &&... args) {
   return new (this->alloc(sizeof(T)).data()) T(std::forward<Args>(args)...);
 }
 
 inline MemArena::MemArena(size_t n) : _reserve_hint(n) {}
 
-inline MemSpan<void> MemArena::Block::remnant()
-{
+inline MemSpan<void> MemArena::Block::remnant() {
   return {this->data() + allocated, this->remaining()};
 }
 
-inline MemArena::Block &MemArena::Block::discard()
-{
+inline MemArena::Block &MemArena::Block::discard() {
   allocated = 0;
   return *this;
 }
 
-inline size_t MemArena::size() const
-{
+inline size_t MemArena::size() const {
   return _active_allocated;
 }
 
-inline size_t MemArena::allocated_size() const
-{
+inline size_t MemArena::allocated_size() const {
   return _frozen_allocated + _active_allocated;
 }
 
-inline size_t MemArena::remaining() const
-{
+inline size_t MemArena::remaining() const {
   return _active.empty() ? 0 : _active.head()->remaining();
 }
 
-inline MemSpan<void> MemArena::remnant()
-{
+inline MemSpan<void> MemArena::remnant() {
   return _active.empty() ? MemSpan<void>() : _active.head()->remnant();
 }
 
-inline size_t MemArena::reserved_size() const
-{
+inline size_t MemArena::reserved_size() const {
   return _active_reserved + _frozen_reserved;
 }
 
-inline auto MemArena::begin() const -> const_iterator
-{
+inline auto MemArena::begin() const -> const_iterator {
   return _active.begin();
 }
 
-inline auto MemArena::end() const -> const_iterator
-{
+inline auto MemArena::end() const -> const_iterator {
   return _active.end();
 }
 
-inline auto MemArena::frozen_begin() const -> const_iterator
-{
+inline auto MemArena::frozen_begin() const -> const_iterator {
   return _frozen.begin();
 }
 
-inline auto MemArena::frozen_end() const -> const_iterator
-{
+inline auto MemArena::frozen_end() const -> const_iterator {
   return _frozen.end();
 }
 
 template <typename T> FixedArena<T>::FixedArena(MemArena &arena) : _arena(arena) {
-  static_assert(sizeof(T) >= sizeof(T*));
+  static_assert(sizeof(T) >= sizeof(T *));
 }
 
-template <typename T> template <typename... Args> T *FixedArena<T>::make(Args... args)
-{
+template <typename T> template <typename... Args> T *FixedArena<T>::make(Args... args) {
   if (_list._next) {
-    void* t = _list._next;
+    void *t     = _list._next;
     _list._next = _list._next->_next;
-    return new (t) T(std::forward(args)...);
+    return new (t) T(std::forward<Args>(args)...);
   }
-  return _arena.template make<T>(std::forward(args)...);
+  return _arena.template make<T>(std::forward<Args>(args)...);
 }
 
-template <typename T> void FixedArena<T>::destroy(T *t)
-{
+template <typename T> void FixedArena<T>::destroy(T *t) {
   if (t) {
     t->~T(); // destructor.
     auto item   = reinterpret_cast<Item *>(t);
@@ -509,4 +491,9 @@ template <typename T> void FixedArena<T>::destroy(T *t)
   }
 }
 
+template<typename T>
+void FixedArena<T>::clear() {
+  _list._next = nullptr;
+}
+
 } // namespace swoc
diff --git a/swoc++/include/swoc/MemSpan.h b/swoc++/include/swoc/MemSpan.h
index 091ecf9..81862c5 100644
--- a/swoc++/include/swoc/MemSpan.h
+++ b/swoc++/include/swoc/MemSpan.h
@@ -43,8 +43,7 @@ namespace swoc
     instances of this class.
 
  */
-template <typename T> class MemSpan
-{
+template <typename T> class MemSpan {
   using self_type = MemSpan; ///< Self reference type.
 
 protected:
@@ -268,8 +267,7 @@ public:
  * One major issue was the array initialization did not work at all if the @c void case didn't
  * exclude that. Once separate there are a number of useful tweaks available.
  */
-template <> class MemSpan<void>
-{
+template <> class MemSpan<void> {
   using self_type = MemSpan; ///< Self reference type.
   template <typename U> friend class MemSpan;
 
@@ -446,21 +444,18 @@ namespace detail
   /// pointer distance calculations for all types, @b including @c <void*>.
   /// This is useful in templates.
   inline size_t
-  ptr_distance(void const *first, void const *last)
-  {
+  ptr_distance(void const *first, void const *last) {
     return static_cast<const char *>(last) - static_cast<const char *>(first);
   }
 
   template <typename T>
   size_t
-  ptr_distance(T const *first, T const *last)
-  {
+  ptr_distance(T const *first, T const *last) {
     return last - first;
   }
 
   inline void *
-  ptr_add(void *ptr, size_t count)
-  {
+  ptr_add(void *ptr, size_t count) {
     return static_cast<char *>(ptr) + count;
   }
 
@@ -488,9 +483,9 @@ namespace detail
 
   template <typename T, typename U>
   size_t
-  is_span_compatible<T, U>::count(size_t size)
-  {
-    if (size % sizeof(U)) {
+  is_span_compatible<T, U>::count(size_t size) {
+    if (size % sizeof(U))
+    {
       throw std::invalid_argument("MemSpan rebind where span size is not a multiple of the element size");
     }
     return size / sizeof(U);
@@ -506,8 +501,7 @@ namespace detail
 
   template <typename T>
   size_t
-  is_span_compatible<T, void>::count(size_t size)
-  {
+  is_span_compatible<T, void>::count(size_t size) {
     return size;
   }
   /// @endcond
@@ -518,22 +512,24 @@ namespace detail
 
 template <typename T>
 int
-memcmp(MemSpan<T> const &lhs, MemSpan<T> const &rhs)
-{
+memcmp(MemSpan<T> const &lhs, MemSpan<T> const &rhs) {
   int zret = 0;
   size_t n = lhs.size();
 
   // Seems a bit ugly but size comparisons must be done anyway to get the memcmp args.
-  if (lhs.count() < rhs.count()) {
+  if (lhs.count() < rhs.count())
+  {
     zret = 1;
-  } else if (lhs.count() > rhs.count()) {
+  } else if (lhs.count() > rhs.count())
+  {
     zret = -1;
     n    = rhs.size();
   }
   // else the counts are equal therefore @a n and @a zret are already correct.
 
   int r = std::memcmp(lhs.data(), rhs.data(), n);
-  if (0 != r) { // If we got a not-equal, override the size based result.
+  if (0 != r)
+  { // If we got a not-equal, override the size based result.
     zret = r;
   }
 
@@ -544,34 +540,29 @@ using std::memcmp;
 
 template <typename T>
 T *
-memcpy(MemSpan<T> &dst, MemSpan<T> const &src)
-{
+memcpy(MemSpan<T> &dst, MemSpan<T> const &src) {
   return static_cast<T *>(std::memcpy(dst.data(), src.data(), std::min(dst.size(), src.size())));
 }
 
 template <typename T>
 T *
-memcpy(MemSpan<T> &dst, T *src)
-{
+memcpy(MemSpan<T> &dst, T *src) {
   return static_cast<T *>(std::memcpy(dst.data(), src, dst.size()));
 }
 
 template <typename T>
 T *
-memcpy(T *dst, MemSpan<T> &src)
-{
+memcpy(T *dst, MemSpan<T> &src) {
   return static_cast<T *>(std::memcpy(dst, src.data(), src.size()));
 }
 
 inline char *
-memcpy(MemSpan<char> &span, std::string_view view)
-{
+memcpy(MemSpan<char> &span, std::string_view view) {
   return static_cast<char *>(std::memcpy(span.data(), view.data(), std::min(view.size(), view.size())));
 }
 
 inline void *
-memcpy(MemSpan<void> &span, std::string_view view)
-{
+memcpy(MemSpan<void> &span, std::string_view view) {
   return std::memcpy(span.data(), view.data(), std::min(view.size(), view.size()));
 }
 
@@ -580,31 +571,28 @@ using std::memcpy;
 
 template <typename T>
 inline MemSpan<T> const &
-memset(MemSpan<T> const &dst, T const &t)
-{
-  for (auto &e : dst) {
+memset(MemSpan<T> const &dst, T const &t) {
+  for (auto &e : dst)
+  {
     e = t;
   }
   return dst;
 }
 
 inline MemSpan<char> const &
-memset(MemSpan<char> const &dst, char c)
-{
+memset(MemSpan<char> const &dst, char c) {
   std::memset(dst.data(), c, dst.size());
   return dst;
 }
 
 inline MemSpan<unsigned char> const &
-memset(MemSpan<unsigned char> const &dst, unsigned char c)
-{
+memset(MemSpan<unsigned char> const &dst, unsigned char c) {
   std::memset(dst.data(), c, dst.size());
   return dst;
 }
 
 inline MemSpan<void> const &
-memset(MemSpan<void> const &dst, char c)
-{
+memset(MemSpan<void> const &dst, char c) {
   std::memset(dst.data(), c, dst.size());
   return dst;
 }
@@ -623,8 +611,7 @@ template <typename T> constexpr MemSpan<T>::MemSpan(std::nullptr_t) {}
 
 template <typename T>
 MemSpan<T> &
-MemSpan<T>::assign(T *ptr, size_t count)
-{
+MemSpan<T>::assign(T *ptr, size_t count) {
   _ptr   = ptr;
   _count = count;
   return *this;
@@ -632,8 +619,7 @@ MemSpan<T>::assign(T *ptr, size_t count)
 
 template <typename T>
 MemSpan<T> &
-MemSpan<T>::assign(T *first, T const *last)
-{
+MemSpan<T>::assign(T *first, T const *last) {
   _ptr   = first;
   _count = detail::ptr_distance(first, last);
   return *this;
@@ -641,8 +627,7 @@ MemSpan<T>::assign(T *first, T const *last)
 
 template <typename T>
 MemSpan<T> &
-MemSpan<T>::clear()
-{
+MemSpan<T>::clear() {
   _ptr   = nullptr;
   _count = 0;
   return *this;
@@ -650,107 +635,91 @@ MemSpan<T>::clear()
 
 template <typename T>
 bool
-MemSpan<T>::is_same(self_type const &that) const
-{
+MemSpan<T>::is_same(self_type const &that) const {
   return _ptr == that._ptr && _count == that._count;
 }
 
 template <typename T>
 bool
-MemSpan<T>::operator==(self_type const &that) const
-{
+MemSpan<T>::operator==(self_type const &that) const {
   return _count == that._count && (_ptr == that._ptr || 0 == memcmp(_ptr, that._ptr, this->size()));
 }
 
 template <typename T>
 bool
-MemSpan<T>::operator!=(self_type const &that) const
-{
+MemSpan<T>::operator!=(self_type const &that) const {
   return !(*this == that);
 }
 
-template <typename T> bool MemSpan<T>::operator!() const
-{
+template <typename T> bool MemSpan<T>::operator!() const {
   return _count == 0;
 }
 
-template <typename T> MemSpan<T>::operator bool() const
-{
+template <typename T> MemSpan<T>::operator bool() const {
   return _count != 0;
 }
 
 template <typename T>
 bool
-MemSpan<T>::empty() const
-{
+MemSpan<T>::empty() const {
   return _count == 0;
 }
 
 template <typename T>
 T *
-MemSpan<T>::begin() const
-{
+MemSpan<T>::begin() const {
   return _ptr;
 }
 
 template <typename T>
 T *
-MemSpan<T>::data() const
-{
+MemSpan<T>::data() const {
   return _ptr;
 }
 
 template <typename T>
 T *
-MemSpan<T>::end() const
-{
+MemSpan<T>::end() const {
   return _ptr + _count;
 }
 
-template <typename T> T &MemSpan<T>::operator[](size_t idx) const
-{
+template <typename T> T &MemSpan<T>::operator[](size_t idx) const {
   return _ptr[idx];
 }
 
 template <typename T>
 size_t
-MemSpan<T>::count() const
-{
+MemSpan<T>::count() const {
   return _count;
 }
 
 template <typename T>
 size_t
-MemSpan<T>::size() const
-{
+MemSpan<T>::size() const {
   return _count * sizeof(T);
 }
 
 template <typename T>
 bool
-MemSpan<T>::contains(T const *ptr) const
-{
+MemSpan<T>::contains(T const *ptr) const {
   return _ptr <= ptr && ptr < _ptr + _count;
 }
 
 template <typename T>
 constexpr auto
-MemSpan<T>::prefix(size_t count) const -> self_type
-{
+MemSpan<T>::prefix(size_t count) const -> self_type {
   return {_ptr, std::min(count, _count)};
 }
 
 template <typename T>
 constexpr auto
-MemSpan<T>::first(size_t count) const -> self_type
-{
+MemSpan<T>::first(size_t count) const -> self_type {
   return this->prefix(count);
 }
 
 template <typename T>
 auto
-MemSpan<T>::remove_prefix(size_t count) -> self_type &
-{
+MemSpan<T>::remove_prefix(size_t count) -> self_type & {
   count = std::min(_count, count);
   _count -= count;
   _ptr += count;
@@ -759,54 +728,48 @@ MemSpan<T>::remove_prefix(size_t count) -> self_type &
 
 template <typename T>
 constexpr auto
-MemSpan<T>::suffix(size_t count) const -> self_type
-{
+MemSpan<T>::suffix(size_t count) const -> self_type {
   count = std::min(_count, count);
   return {(_ptr + _count) - count, count};
 }
 
 template <typename T>
 constexpr MemSpan<T>
-MemSpan<T>::last(size_t count) const
-{
+MemSpan<T>::last(size_t count) const {
   return this->suffix(count);
 }
 
 template <typename T>
 MemSpan<T> &
-MemSpan<T>::remove_suffix(size_t count)
-{
+MemSpan<T>::remove_suffix(size_t count) {
   _count -= std::min(count, _count);
   return *this;
 }
 
 template <typename T>
 constexpr MemSpan<T>
-MemSpan<T>::subspan(size_t offset, size_t count) const
-{
+MemSpan<T>::subspan(size_t offset, size_t count) const {
   return offset <= _count ? self_type{this->data() + offset, std::min(count, _count - offset)} : self_type{};
 }
 
 template <typename T>
 T &
-MemSpan<T>::front()
-{
+MemSpan<T>::front() {
   return *_ptr;
 }
 
 template <typename T>
 T &
-MemSpan<T>::back()
-{
+MemSpan<T>::back() {
   return _ptr[_count - 1];
 }
 
 template <typename T>
 template <typename F>
 MemSpan<T> &
-MemSpan<T>::apply(F &&f)
-{
-  for (auto &item : *this) {
+MemSpan<T>::apply(F &&f) {
+  for (auto &item : *this)
+  {
     f(item);
   }
   return *this;
@@ -815,8 +778,7 @@ MemSpan<T>::apply(F &&f)
 template <typename T>
 template <typename U>
 MemSpan<U>
-MemSpan<T>::rebind() const
-{
+MemSpan<T>::rebind() const {
   static_assert(detail::is_span_compatible<T, U>::value,
                 "MemSpan only allows rebinding between types who sizes are integral multiples.");
   return {static_cast<U *>(static_cast<void *>(_ptr)), detail::is_span_compatible<T, U>::count(this->size())};
@@ -824,8 +786,7 @@ MemSpan<T>::rebind() const
 
 template <typename T>
 std::string_view
-MemSpan<T>::view() const
-{
+MemSpan<T>::view() const {
   return {static_cast<const char *>(_ptr), this->size()};
 }
 
@@ -840,111 +801,94 @@ inline MemSpan<void>::MemSpan(value_type *first, value_type *last) : _ptr{first}
 inline constexpr MemSpan<void>::MemSpan(std::nullptr_t) {}
 
 inline MemSpan<void> &
-MemSpan<void>::assign(value_type *ptr, size_t n)
-{
+MemSpan<void>::assign(value_type *ptr, size_t n) {
   _ptr  = ptr;
   _size = n;
   return *this;
 }
 
 inline MemSpan<void> &
-MemSpan<void>::assign(value_type *first, value_type const *last)
-{
+MemSpan<void>::assign(value_type *first, value_type const *last) {
   _ptr  = first;
   _size = detail::ptr_distance(first, last);
   return *this;
 }
 
 inline MemSpan<void> &
-MemSpan<void>::clear()
-{
+MemSpan<void>::clear() {
   _ptr  = nullptr;
   _size = 0;
   return *this;
 }
 
 inline bool
-MemSpan<void>::is_same(self_type const &that) const
-{
+MemSpan<void>::is_same(self_type const &that) const {
   return _ptr == that._ptr && _size == that._size;
 }
 
 inline bool
-MemSpan<void>::operator==(self_type const &that) const
-{
+MemSpan<void>::operator==(self_type const &that) const {
   return _size == that._size && (_ptr == that._ptr || 0 == memcmp(_ptr, that._ptr, _size));
 }
 
 inline bool
-MemSpan<void>::operator!=(self_type const &that) const
-{
+MemSpan<void>::operator!=(self_type const &that) const {
   return !(*this == that);
 }
 
-inline bool MemSpan<void>::operator!() const
-{
+inline bool MemSpan<void>::operator!() const {
   return _size == 0;
 }
 
-inline MemSpan<void>::operator bool() const
-{
+inline MemSpan<void>::operator bool() const {
   return _size != 0;
 }
 
 inline bool
-MemSpan<void>::empty() const
-{
+MemSpan<void>::empty() const {
   return _size == 0;
 }
 
 inline void *
-MemSpan<void>::data() const
-{
+MemSpan<void>::data() const {
   return _ptr;
 }
 
 inline void *
-MemSpan<void>::data_end() const
-{
+MemSpan<void>::data_end() const {
   return detail::ptr_add(_ptr, _size);
 }
 
 inline size_t
-MemSpan<void>::size() const
-{
+MemSpan<void>::size() const {
   return _size;
 }
 
 template <typename U>
 auto
-MemSpan<void>::operator=(MemSpan<U> const &that) -> self_type &
-{
+MemSpan<void>::operator=(MemSpan<U> const &that) -> self_type & {
   _ptr  = that._ptr;
   _size = that.size();
   return *this;
 }
 
 inline constexpr MemSpan<void>
-MemSpan<void>::subspan(size_t offset, size_t count) const
-{
+MemSpan<void>::subspan(size_t offset, size_t count) const {
   return offset <= _size ? self_type{detail::ptr_add(this->data(), offset), std::min(count, _size - offset)} : self_type{};
 }
 
 inline bool
-MemSpan<void>::contains(value_type const *ptr) const
-{
+MemSpan<void>::contains(value_type const *ptr) const {
   return _ptr <= ptr && ptr < this->data_end();
 }
 
 inline MemSpan<void>
-MemSpan<void>::prefix(size_t n) const
-{
+MemSpan<void>::prefix(size_t n) const {
   return {_ptr, std::min(n, _size)};
 }
 
 inline MemSpan<void> &
-MemSpan<void>::remove_prefix(size_t n)
-{
+MemSpan<void>::remove_prefix(size_t n) {
   n = std::max(_size, n);
   _size -= n;
   _ptr = static_cast<char *>(_ptr) + n;
@@ -952,37 +896,32 @@ MemSpan<void>::remove_prefix(size_t n)
 }
 
 inline MemSpan<void>
-MemSpan<void>::suffix(size_t count) const
-{
+MemSpan<void>::suffix(size_t count) const {
   count = std::max(count, _size);
   return {static_cast<char *>(this->data_end()) - count, count};
 }
 
 inline MemSpan<void> &
-MemSpan<void>::remove_suffix(size_t count)
-{
+MemSpan<void>::remove_suffix(size_t count) {
   _size -= std::max(count, _size);
   return *this;
 }
 
 template <typename U>
 MemSpan<U>
-MemSpan<void>::rebind() const
-{
+MemSpan<void>::rebind() const {
   return {static_cast<U *>(_ptr), detail::is_span_compatible<void, U>::count(_size)};
 }
 
 // Specialize so that @c void -> @c void rebinding compiles and works as expected.
 template <>
 inline MemSpan<void>
-MemSpan<void>::rebind() const
-{
+MemSpan<void>::rebind() const {
   return *this;
 }
 
 inline std::string_view
-MemSpan<void>::view() const
-{
+MemSpan<void>::view() const {
   return {static_cast<char const *>(_ptr), _size};
 }
 
@@ -993,26 +932,21 @@ MemSpan<void>::view() const
 // and size.
 namespace std
 {
-template <size_t IDX, typename R> class tuple_element<IDX, swoc::MemSpan<R>>
-{
+template <size_t IDX, typename R> class tuple_element<IDX, swoc::MemSpan<R>> {
   static_assert("swoc::MemSpan tuple index out of range");
 };
 
-template <typename R> class tuple_element<0, swoc::MemSpan<R>>
-{
+template <typename R> class tuple_element<0, swoc::MemSpan<R>> {
 public:
   using type = R *;
 };
 
-template <typename R> class tuple_element<1, swoc::MemSpan<R>>
-{
+template <typename R> class tuple_element<1, swoc::MemSpan<R>> {
 public:
   using type = size_t;
 };
 
-template <typename R> class tuple_size<swoc::MemSpan<R>> : public std::integral_constant<size_t, 2>
-{
-};
+template <typename R> class tuple_size<swoc::MemSpan<R>> : public std::integral_constant<size_t, 2> {};
 
 }; // namespace std
 
diff --git a/swoc++/include/swoc/RBTree.h b/swoc++/include/swoc/RBTree.h
index 868cb84..8e08b77 100644
--- a/swoc++/include/swoc/RBTree.h
+++ b/swoc++/include/swoc/RBTree.h
@@ -1,25 +1,11 @@
+#pragma once
+// SPDX-License-Identifier: Apache-2.0
 /** @file
 
    Red/Black tree.
-
-   The primary purpose of this class is to provide access to structural changes in the
-   tree so that subtree data can be maintained.
 */
-/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
-   See the NOTICE file distributed with this work for additional information regarding copyright
-   ownership.  The ASF licenses this file to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance with the License.  You may obtain a
-   copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
 
-   Unless required by applicable law or agreed to in writing, software distributed under the License
-   is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-   or implied. See the License for the specific language governing permissions and limitations under
-   the License.
- */
-
-#pragma once
+#include <swoc/IntrusiveDList.h>
 
 namespace swoc
 {
@@ -30,6 +16,10 @@ namespace detail
       This class provides only the basic tree operations. The container must provide the search and
       decision logic. This enables this class to be a base class for templated nodes with much less
       code duplication.
+
+      @internal The primary point of a custom implementation is to hook on rotation events which
+      are used to update additional tree data. This provides a significant performance boost
+      for range based uses of this class.
   */
   struct RBNode {
     using self_type = RBNode; ///< self reference type
@@ -135,10 +125,7 @@ namespace detail
 
         @internal This is the primary reason for this class.
     */
-    virtual void
-    structure_fixup()
-    {
-    }
+    virtual void structure_fixup() {}
 
     /** Called from @c validate to perform any additional validation checks.
         Clients should chain this if they wish to perform additional checks.
@@ -192,31 +179,39 @@ namespace detail
     self_type *_right{nullptr};  ///< right child
     self_type *_next{nullptr};   ///< Next node.
     self_type *_prev{nullptr};   ///< Previous node.
+
+    /// Support for @c IntrusiveDList
+    struct Linkage {
+      static self_type *& next_ptr(self_type *t) {
+        return swoc::ptr_ref_cast<self_type>(t->_next);
+      }
+      static self_type *& prev_ptr(self_type *t) {
+        return swoc::ptr_ref_cast<self_type>(t->_prev);
+      }
+    };
+
+
   };
 
   // --- Implementation ---
 
   inline auto
-  RBNode::direction_of(self_type *const &n) const -> Direction
-  {
+  RBNode::direction_of(self_type *const &n) const -> Direction {
     return (n == _left) ? Direction::LEFT : (n == _right) ? Direction::RIGHT : Direction::NONE;
   }
 
   inline RBNode::Color
-  RBNode::color() const
-  {
+  RBNode::color() const {
     return _color;
   }
 
   inline RBNode::Direction
-  RBNode::flip(RBNode::Direction d)
-  {
+  RBNode::flip(RBNode::Direction d) {
     return Direction::LEFT == d ? Direction::RIGHT : Direction::RIGHT == d ? Direction::LEFT : Direction::NONE;
   }
 
   inline void
-  RBNode::clear_child(RBNode::Direction dir)
-  {
+  RBNode::clear_child(RBNode::Direction dir) {
     if (Direction::LEFT == dir)
       _left = nullptr;
     else if (Direction::RIGHT == dir)
@@ -224,11 +219,10 @@ namespace detail
   }
 
   inline bool
-  RBNode::structure_validate()
-  {
+  RBNode::structure_validate() {
     return true;
   }
 
-} /* namespace detail */
+} // namespace detail
 
 } // namespace swoc
diff --git a/swoc++/include/swoc/Scalar.h b/swoc++/include/swoc/Scalar.h
index 0619d62..ac6b028 100644
--- a/swoc++/include/swoc/Scalar.h
+++ b/swoc++/include/swoc/Scalar.h
@@ -56,35 +56,37 @@ namespace detail
   // Convert a count @a c that is scale @a S to scale @c N
   template <intmax_t N, intmax_t S, typename C>
   C
-  scale_conversion_round_up(C c)
-  {
+  scale_conversion_round_up(C c) {
     typedef std::ratio<N, S> R;
-    if (N == S) {
+    if (N == S)
+    {
       return c;
-    } else if (R::den == 1) {
+    } else if (R::den == 1)
+    {
       return c / R::num + (0 != c % R::num); // N is a multiple of S.
-    } else if (R::num == 1) {
+    } else if (R::num == 1)
+    {
       return c * R::den; // S is a multiple of N.
-    } else {
-      return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num + (0 != (c % R::num));
-    }
+    } else
+    { return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num + (0 != (c % R::num)); }
   }
 
   // Convert a count @a c that is scale @a S to scale @c N
   template <intmax_t N, intmax_t S, typename C>
   C
-  scale_conversion_round_down(C c)
-  {
+  scale_conversion_round_down(C c) {
     typedef std::ratio<N, S> R;
-    if (N == S) {
+    if (N == S)
+    {
       return c;
-    } else if (R::den == 1) {
+    } else if (R::den == 1)
+    {
       return c / R::num; // N = k S
-    } else if (R::num == 1) {
+    } else if (R::num == 1)
+    {
       return c * R::den; // S = k N
-    } else {
-      return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num;
-    }
+    } else
+    { return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num; }
   }
 
   /* Helper classes for @c Scalar
@@ -121,8 +123,7 @@ namespace detail
     //    template <typename I> constexpr operator scalar_unit_round_up_t<I>() { return {static_cast<I>(_count)}; }
     template <intmax_t N, typename I>
     constexpr I
-    scale() const
-    {
+    scale() const {
       return static_cast<I>(_n / N + (0 != (_n % N)));
     }
   };
@@ -133,8 +134,7 @@ namespace detail
     //    template <typename I> operator scalar_unit_round_down_t<I>() { return {static_cast<I>(_count)}; }
     template <intmax_t N, typename I>
     constexpr I
-    scale() const
-    {
+    scale() const {
       return static_cast<I>(_n / N);
     }
   };
@@ -142,8 +142,7 @@ namespace detail
   // Scalar value, to be rounded up.
   template <intmax_t N, typename C, typename T> struct scalar_round_up_t {
     C _n;
-    template <intmax_t S, typename I> constexpr operator Scalar<S, I, T>() const
-    {
+    template <intmax_t S, typename I> constexpr operator Scalar<S, I, T>() const {
       return Scalar<S, I, T>(scale_conversion_round_up<S, N>(_n));
     }
   };
@@ -151,8 +150,7 @@ namespace detail
   // Scalar value, to be rounded down.
   template <intmax_t N, typename C, typename T> struct scalar_round_down_t {
     C _n;
-    template <intmax_t S, typename I> constexpr operator Scalar<S, I, T>() const
-    {
+    template <intmax_t S, typename I> constexpr operator Scalar<S, I, T>() const {
       return Scalar<S, I, T>(scale_conversion_round_down<S, N>(_n));
     }
   };
@@ -184,8 +182,7 @@ namespace detail
     @see round_up
     @see round_down
  */
-template <intmax_t N, typename C = int, typename T = tag::generic> class Scalar
-{
+template <intmax_t N, typename C = int, typename T = tag::generic> class Scalar {
   typedef Scalar self_type; ///< Self reference type.
 
 public:
@@ -361,135 +358,110 @@ template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(
 template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(self_type const &that) : _n(that._n) {}
 template <intmax_t N, typename C, typename T>
 template <typename I>
-constexpr Scalar<N, C, T>::Scalar(Scalar<N, I, T> const &that) : _n(static_cast<C>(that.count()))
-{
-}
+constexpr Scalar<N, C, T>::Scalar(Scalar<N, I, T> const &that) : _n(static_cast<C>(that.count())) {}
 template <intmax_t N, typename C, typename T>
 template <intmax_t S, typename I>
-constexpr Scalar<N, C, T>::Scalar(Scalar<S, I, T> const &that) : _n(std::ratio<S, N>::num * that.count())
-{
+constexpr Scalar<N, C, T>::Scalar(Scalar<S, I, T> const &that) : _n(std::ratio<S, N>::num * that.count()) {
   static_assert(std::ratio<S, N>::den == 1,
                 "Construction not permitted - target scale is not an integral multiple of source scale.");
 }
 template <intmax_t N, typename C, typename T>
-constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_up_t<N, C, T> const &v) : _n(v._n)
-{
-}
+constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_up_t<N, C, T> const &v) : _n(v._n) {}
 template <intmax_t N, typename C, typename T>
-constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_down_t<N, C, T> const &v) : _n(v._n)
-{
-}
+constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_down_t<N, C, T> const &v) : _n(v._n) {}
 template <intmax_t N, typename C, typename T>
 template <typename I>
-constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_up_t<I> v) : _n(v.template scale<N, C>())
-{
-}
+constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_up_t<I> v) : _n(v.template scale<N, C>()) {}
 template <intmax_t N, typename C, typename T>
 template <typename I>
-constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_down_t<I> v) : _n(v.template scale<N, C>())
-{
-}
+constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_down_t<I> v) : _n(v.template scale<N, C>()) {}
 
 template <intmax_t N, typename C, typename T>
 constexpr auto
-Scalar<N, C, T>::count() const -> Counter
-{
+Scalar<N, C, T>::count() const -> Counter {
   return _n;
 }
 
 template <intmax_t N, typename C, typename T>
 constexpr intmax_t
-Scalar<N, C, T>::value() const
-{
+Scalar<N, C, T>::value() const {
   return _n * SCALE;
 }
 
-template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::operator intmax_t() const
-{
+template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::operator intmax_t() const {
   return _n * SCALE;
 }
 
 template <intmax_t N, typename C, typename T>
 inline auto
-Scalar<N, C, T>::assign(Counter n) -> self_type &
-{
+Scalar<N, C, T>::assign(Counter n) -> self_type & {
   _n = n;
   return *this;
 }
 
 template <intmax_t N, typename C, typename T>
 inline auto
-Scalar<N, C, T>::operator=(self_type const &that) -> self_type &
-{
+Scalar<N, C, T>::operator=(self_type const &that) -> self_type & {
   _n = that._n;
   return *this;
 }
 
 template <intmax_t N, typename C, typename T>
 inline auto
-Scalar<N, C, T>::operator=(detail::scalar_round_up_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::operator=(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
   _n = v._n;
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 inline auto
-Scalar<N, C, T>::assign(detail::scalar_round_up_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::assign(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
   _n = v._n;
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 inline auto
-Scalar<N, C, T>::operator=(detail::scalar_round_down_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::operator=(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
   _n = v._n;
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 inline auto
-Scalar<N, C, T>::assign(detail::scalar_round_down_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::assign(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
   _n = v._n;
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 template <typename I>
 inline auto
-Scalar<N, C, T>::operator=(detail::scalar_unit_round_up_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::operator=(detail::scalar_unit_round_up_t<I> v) -> self_type & {
   _n = v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 template <typename I>
 inline auto
-Scalar<N, C, T>::assign(detail::scalar_unit_round_up_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::assign(detail::scalar_unit_round_up_t<I> v) -> self_type & {
   _n = v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 template <typename I>
 inline auto
-Scalar<N, C, T>::operator=(detail::scalar_unit_round_down_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::operator=(detail::scalar_unit_round_down_t<I> v) -> self_type & {
   _n = v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 template <typename I>
 inline auto
-Scalar<N, C, T>::assign(detail::scalar_unit_round_down_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::assign(detail::scalar_unit_round_down_t<I> v) -> self_type & {
   _n = v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 template <intmax_t S, typename I>
 auto
-Scalar<N, C, T>::operator=(Scalar<S, I, T> const &that) -> self_type &
-{
+Scalar<N, C, T>::operator=(Scalar<S, I, T> const &that) -> self_type & {
   typedef std::ratio<S, N> R;
   static_assert(R::den == 1, "Assignment not permitted - target scale is not an integral multiple of source scale.");
   _n = that.count() * R::num;
@@ -498,8 +470,7 @@ Scalar<N, C, T>::operator=(Scalar<S, I, T> const &that) -> self_type &
 template <intmax_t N, typename C, typename T>
 template <intmax_t S, typename I>
 auto
-Scalar<N, C, T>::assign(Scalar<S, I, T> const &that) -> self_type &
-{
+Scalar<N, C, T>::assign(Scalar<S, I, T> const &that) -> self_type & {
   typedef std::ratio<S, N> R;
   static_assert(R::den == 1, "Assignment not permitted - target scale is not an integral multiple of source scale.");
   _n = that.count() * R::num;
@@ -508,8 +479,7 @@ Scalar<N, C, T>::assign(Scalar<S, I, T> const &that) -> self_type &
 
 template <intmax_t N, typename C, typename T>
 constexpr inline intmax_t
-Scalar<N, C, T>::scale()
-{
+Scalar<N, C, T>::scale() {
   return SCALE;
 }
 
@@ -523,8 +493,7 @@ Scalar<N, C, T>::scale()
  */
 template <typename C>
 constexpr detail::scalar_unit_round_up_t<C>
-round_up(C n)
-{
+round_up(C n) {
   return {n};
 }
 
@@ -538,8 +507,7 @@ round_up(C n)
  */
 template <intmax_t N, typename C, typename T>
 constexpr detail::scalar_round_up_t<N, C, T>
-round_up(Scalar<N, C, T> v)
-{
+round_up(Scalar<N, C, T> v) {
   return {v.count()};
 }
 
@@ -551,8 +519,7 @@ round_up(Scalar<N, C, T> v)
  */
 template <typename C>
 constexpr detail::scalar_unit_round_down_t<C>
-round_down(C n)
-{
+round_down(C n) {
   return {n};
 }
 
@@ -566,8 +533,7 @@ round_down(C n)
  */
 template <intmax_t N, typename C, typename T>
 constexpr detail::scalar_round_down_t<N, C, T>
-round_down(Scalar<N, C, T> v)
-{
+round_down(Scalar<N, C, T> v) {
   return {v.count()};
 }
 
@@ -576,24 +542,21 @@ round_down(Scalar<N, C, T> v)
 
 template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
 bool
-operator<(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
-{
+operator<(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs) {
   typedef std::ratio<N, S> R;
   return lhs.count() * R::num < rhs.count() * R::den;
 }
 
 template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
 bool
-operator==(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
-{
+operator==(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs) {
   typedef std::ratio<N, S> R;
   return lhs.count() * R::num == rhs.count() * R::den;
 }
 
 template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
 bool
-operator<=(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
-{
+operator<=(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs) {
   typedef std::ratio<N, S> R;
   return lhs.count() * R::num <= rhs.count() * R::den;
 }
@@ -601,23 +564,20 @@ operator<=(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
 // Derived compares.
 template <intmax_t N, typename C, intmax_t S, typename I, typename T>
 bool
-operator>(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs)
-{
+operator>(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs) {
   return rhs < lhs;
 }
 
 template <intmax_t N, typename C, intmax_t S, typename I, typename T>
 bool
-operator>=(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs)
-{
+operator>=(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs) {
   return rhs <= lhs;
 }
 
 // Arithmetic operators
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator+=(self_type const &that) -> self_type &
-{
+Scalar<N, C, T>::operator+=(self_type const &that) -> self_type & {
   _n += that._n;
   return *this;
 }
@@ -625,8 +585,7 @@ Scalar<N, C, T>::operator+=(self_type const &that) -> self_type &
 template <intmax_t N, typename C, typename T>
 template <intmax_t S, typename I>
 auto
-Scalar<N, C, T>::operator+=(Scalar<S, I, T> const &that) -> self_type &
-{
+Scalar<N, C, T>::operator+=(Scalar<S, I, T> const &that) -> self_type & {
   typedef std::ratio<S, N> R;
   static_assert(R::den == 1, "Addition not permitted - target scale is not an integral multiple of source scale.");
   _n += that.count() * R::num;
@@ -636,8 +595,7 @@ Scalar<N, C, T>::operator+=(Scalar<S, I, T> const &that) -> self_type &
 template <intmax_t N, typename C, typename T>
 template <typename I>
 auto
-Scalar<N, C, T>::operator+=(detail::scalar_unit_round_up_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::operator+=(detail::scalar_unit_round_up_t<I> v) -> self_type & {
   _n += v.template scale<N, C>();
   return *this;
 }
@@ -645,95 +603,81 @@ Scalar<N, C, T>::operator+=(detail::scalar_unit_round_up_t<I> v) -> self_type &
 template <intmax_t N, typename C, typename T>
 template <typename I>
 auto
-Scalar<N, C, T>::operator+=(detail::scalar_unit_round_down_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::operator+=(detail::scalar_unit_round_down_t<I> v) -> self_type & {
   _n += v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator+=(detail::scalar_round_up_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::operator+=(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
   _n += v._n;
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator+=(detail::scalar_round_down_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::operator+=(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
   _n += v._n;
   return *this;
 }
 
 template <intmax_t N, typename C, intmax_t S, typename I, typename T>
 auto
-operator+(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type
-{
+operator+(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type {
   return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) += rhs;
 }
 
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator+(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs)
-{
+operator+(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(lhs) += rhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator+(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs)
-{
+operator+(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(rhs) += lhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs)
-{
+operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs) {
   return Scalar<N, C, T>(lhs) += rhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator+(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs)
-{
+operator+(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(rhs) += lhs;
 }
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs)
-{
+operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs) {
   return Scalar<N, C, T>(lhs) += rhs;
 }
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator+(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
-{
+operator+(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(rhs) += lhs._n;
 }
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs)
-{
+operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs) {
   return Scalar<N, C, T>(lhs) += rhs._n;
 }
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator+(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
-{
+operator+(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(rhs) += lhs._n;
 }
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs)
-{
+operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs) {
   return Scalar<N, C, T>(lhs) += rhs._n;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator-=(self_type const &that) -> self_type &
-{
+Scalar<N, C, T>::operator-=(self_type const &that) -> self_type & {
   _n -= that._n;
   return *this;
 }
@@ -741,8 +685,7 @@ Scalar<N, C, T>::operator-=(self_type const &that) -> self_type &
 template <intmax_t N, typename C, typename T>
 template <intmax_t S, typename I>
 auto
-Scalar<N, C, T>::operator-=(Scalar<S, I, T> const &that) -> self_type &
-{
+Scalar<N, C, T>::operator-=(Scalar<S, I, T> const &that) -> self_type & {
   typedef std::ratio<S, N> R;
   static_assert(R::den == 1, "Subtraction not permitted - target scale is not an integral multiple of source scale.");
   _n -= that.count() * R::num;
@@ -752,116 +695,100 @@ Scalar<N, C, T>::operator-=(Scalar<S, I, T> const &that) -> self_type &
 template <intmax_t N, typename C, typename T>
 template <typename I>
 auto
-Scalar<N, C, T>::operator-=(detail::scalar_unit_round_up_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::operator-=(detail::scalar_unit_round_up_t<I> v) -> self_type & {
   _n -= v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 template <typename I>
 auto
-Scalar<N, C, T>::operator-=(detail::scalar_unit_round_down_t<I> v) -> self_type &
-{
+Scalar<N, C, T>::operator-=(detail::scalar_unit_round_down_t<I> v) -> self_type & {
   _n -= v.template scale<N, C>();
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator-=(detail::scalar_round_up_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::operator-=(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
   _n -= v._n;
   return *this;
 }
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator-=(detail::scalar_round_down_t<N, C, T> v) -> self_type &
-{
+Scalar<N, C, T>::operator-=(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
   _n -= v._n;
   return *this;
 }
 
 template <intmax_t N, typename C, intmax_t S, typename I, typename T>
 auto
-operator-(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type
-{
+operator-(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type {
   return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator-(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs)
-{
+operator-(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(lhs) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator-(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs)
-{
+operator-(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(lhs.template scale<N, C>()) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs)
-{
+operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs) {
   return Scalar<N, C, T>(lhs) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator-(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs)
-{
+operator-(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(lhs.template scale<N, C>()) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs)
-{
+operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs) {
   return Scalar<N, C, T>(lhs) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator-(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
-{
+operator-(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(lhs._n) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs)
-{
+operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs) {
   return Scalar<N, C, T>(lhs) -= rhs._n;
 }
 
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator-(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
-{
+operator-(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(lhs._n) -= rhs;
 }
 
 template <intmax_t N, typename C, typename T>
 Scalar<N, C, T>
-operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs)
-{
+operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs) {
   return Scalar<N, C, T>(lhs) -= rhs._n;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator++() -> self_type &
-{
+Scalar<N, C, T>::operator++() -> self_type & {
   ++_n;
   return *this;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator++(int) -> self_type
-{
+Scalar<N, C, T>::operator++(int) -> self_type {
   self_type zret(*this);
   ++_n;
   return zret;
@@ -869,16 +796,14 @@ Scalar<N, C, T>::operator++(int) -> self_type
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator--() -> self_type &
-{
+Scalar<N, C, T>::operator--() -> self_type & {
   --_n;
   return *this;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator--(int) -> self_type
-{
+Scalar<N, C, T>::operator--(int) -> self_type {
   self_type zret(*this);
   --_n;
   return zret;
@@ -886,95 +811,80 @@ Scalar<N, C, T>::operator--(int) -> self_type
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::inc(Counter n) -> self_type &
-{
+Scalar<N, C, T>::inc(Counter n) -> self_type & {
   _n += n;
   return *this;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::dec(Counter n) -> self_type &
-{
+Scalar<N, C, T>::dec(Counter n) -> self_type & {
   _n -= n;
   return *this;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator*=(C n) -> self_type &
-{
+Scalar<N, C, T>::operator*=(C n) -> self_type & {
   _n *= n;
   return *this;
 }
 
-template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(Scalar<N, C, T> const &lhs, C n)
-{
+template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(Scalar<N, C, T> const &lhs, C n) {
   return Scalar<N, C, T>(lhs) *= n;
 }
-template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(C n, Scalar<N, C, T> const &rhs)
-{
+template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(C n, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(rhs) *= n;
 }
-template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(Scalar<N, C, T> const &lhs, int n)
-{
+template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(Scalar<N, C, T> const &lhs, int n) {
   return Scalar<N, C, T>(lhs) *= n;
 }
-template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(int n, Scalar<N, C, T> const &rhs)
-{
+template <intmax_t N, typename C, typename T> Scalar<N, C, T> operator*(int n, Scalar<N, C, T> const &rhs) {
   return Scalar<N, C, T>(rhs) *= n;
 }
-template <intmax_t N> Scalar<N, int> operator*(Scalar<N, int> const &lhs, int n)
-{
+template <intmax_t N> Scalar<N, int> operator*(Scalar<N, int> const &lhs, int n) {
   return Scalar<N, int>(lhs) *= n;
 }
-template <intmax_t N> Scalar<N, int> operator*(int n, Scalar<N, int> const &rhs)
-{
+template <intmax_t N> Scalar<N, int> operator*(int n, Scalar<N, int> const &rhs) {
   return Scalar<N, int>(rhs) *= n;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator/=(C n) -> self_type &
-{
+Scalar<N, C, T>::operator/=(C n) -> self_type & {
   _n /= n;
   return *this;
 }
 
 template <intmax_t N, typename C, intmax_t S, typename I, typename T>
 auto
-operator/(Scalar<N, C, T> lhs, Scalar<S, I, T> rhs) -> typename std::common_type<C, I>::type
-{
+operator/(Scalar<N, C, T> lhs, Scalar<S, I, T> rhs) -> typename std::common_type<C, I>::type {
   using R = std::ratio<N, S>;
   return (lhs.count() * R::num) / (rhs.count() * R::den);
 }
 
 template <intmax_t N, typename C, typename T, typename I>
 Scalar<N, C, T>
-operator/(Scalar<N, C, T> lhs, I n)
-{
+operator/(Scalar<N, C, T> lhs, I n) {
   static_assert(std::is_integral<I>::value, "Scalar divsion only support integral types.");
   return Scalar<N, C, T>(lhs) /= n;
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::operator()(Counter n) const -> self_type
-{
+Scalar<N, C, T>::operator()(Counter n) const -> self_type {
   return self_type{n};
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::plus(Counter n) const -> self_type
-{
+Scalar<N, C, T>::plus(Counter n) const -> self_type {
   return {_n + n};
 }
 
 template <intmax_t N, typename C, typename T>
 auto
-Scalar<N, C, T>::minus(Counter n) const -> self_type
-{
+Scalar<N, C, T>::minus(Counter n) const -> self_type {
   return {_n - n};
 }
 
@@ -993,8 +903,7 @@ Scalar<N, C, T>::minus(Counter n) const -> self_type
  */
 template <intmax_t N, typename C>
 C
-round_up(C value)
-{
+round_up(C value) {
   return N * detail::scale_conversion_round_up<N, 1>(value);
 }
 
@@ -1013,8 +922,7 @@ round_up(C value)
  */
 template <intmax_t N, typename C>
 C
-round_down(C value)
-{
+round_down(C value) {
   return N * detail::scale_conversion_round_down<N, 1>(value);
 }
 
@@ -1022,21 +930,17 @@ namespace detail
 {
   template <typename T>
   auto
-  tag_label(std::ostream &, const meta::CaseTag<0> &) -> void
-  {
-  }
+  tag_label(std::ostream &, const meta::CaseTag<0> &) -> void {}
 
   template <typename T>
   auto
-  tag_label(std::ostream &w, const meta::CaseTag<1> &) -> decltype(T::label, meta::TypeFunc<void>())
-  {
+  tag_label(std::ostream &w, const meta::CaseTag<1> &) -> decltype(T::label, meta::TypeFunc<void>()) {
     w << T::label;
   }
 
   template <typename T>
   inline std::ostream &
-  tag_label(std::ostream &w)
-  {
+  tag_label(std::ostream &w) {
     tag_label<T>(w, meta::CaseArg);
     return w;
   }
@@ -1056,8 +960,7 @@ struct common_type<swoc::Scalar<N, C, T>, swoc::Scalar<S, I, T>> {
 
 template <intmax_t N, typename C, typename T>
 ostream &
-operator<<(ostream &s, swoc::Scalar<N, C, T> const &x)
-{
+operator<<(ostream &s, swoc::Scalar<N, C, T> const &x) {
   s << x.value();
   return swoc::detail::tag_label<T>(s);
 }
diff --git a/swoc++/include/swoc/TextView.h b/swoc++/include/swoc/TextView.h
index 8fff1b7..49d0909 100644
--- a/swoc++/include/swoc/TextView.h
+++ b/swoc++/include/swoc/TextView.h
@@ -78,8 +78,7 @@ int memcmp(const std::string_view &lhs, const std::string_view &rhs);
  * @see memcmp
  */
 inline int
-strcmp(const std::string_view &lhs, const std::string_view &rhs)
-{
+strcmp(const std::string_view &lhs, const std::string_view &rhs) {
   return memcmp(lhs, rhs);
 }
 
@@ -99,8 +98,7 @@ class TextView;
     Constructors require either a literal string or an explicit length. This avoid ambiguities which
     are much more annoying that explicitly calling @c strlen on a character pointer.
  */
-class TextView : public std::string_view
-{
+class TextView : public std::string_view {
   using self_type  = TextView;         ///< Self reference type.
   using super_type = std::string_view; ///< Parent type.
 
@@ -267,14 +265,6 @@ public:
    */
   self_type &rtrim(std::string_view const &delimiters);
 
-  /** Remove bytes from the end of the view that are in @a delimiters.
-   *
-   * @return @c *this
-   *
-   * @internal This is needed to avoid collisions with the templated predicate style.
-   */
-  self_type &rtrim(const char *delimiters);
-
   /** Remove bytes from the end of the view for which @a pred is @c true.
    *
    * @a pred must be a functor taking a @c char argument and returning @c bool.
@@ -712,8 +702,7 @@ public:
   /// Ordering functor, lexicographic comparison.
   struct LessThan {
     bool
-    operator()(self_type const &lhs, self_type const &rhs)
-    {
+    operator()(self_type const &lhs, self_type const &rhs) {
       return -1 == strcmp(lhs, rhs);
     }
   };
@@ -721,8 +710,7 @@ public:
   /// Ordering functor, case ignoring lexicographic comparison.
   struct LessThanNoCase {
     bool
-    operator()(self_type const &lhs, self_type const &rhs)
-    {
+    operator()(self_type const &lhs, self_type const &rhs) {
       return -1 == strcasecmp(lhs, rhs);
     }
   };
@@ -813,14 +801,15 @@ uintmax_t svtou(TextView src, TextView *parsed = nullptr, int base = 0);
  */
 template <int N>
 uintmax_t
-svto_radix(swoc::TextView &src)
-{
+svto_radix(swoc::TextView &src) {
   static_assert(0 < N && N <= 36, "Radix must be in the range 1..36");
   uintmax_t zret{0};
   int8_t v;
-  while (src.size() && (0 <= (v = swoc::svtoi_convert[uint8_t(*src)])) && v < N) {
+  while (src.size() && (0 <= (v = swoc::svtoi_convert[uint8_t(*src)])) && v < N)
+  {
     auto n = zret * N + v;
-    if (n < zret) { // overflow / wrap
+    if (n < zret)
+    { // overflow / wrap
       return std::numeric_limits<uintmax_t>::max();
     }
     zret = n;
@@ -831,8 +820,7 @@ svto_radix(swoc::TextView &src)
 
 template <int N>
 uintmax_t
-svto_radix(swoc::TextView &&src)
-{
+svto_radix(swoc::TextView &&src) {
   return svto_radix<N>(src);
 }
 // ----------------------------------------------------------
@@ -851,126 +839,110 @@ template <size_t N> constexpr TextView::TextView(const char (&s)[N]) : super_typ
 template <size_t N> constexpr TextView::TextView(const char (&s)[N], size_t n) : super_type(s, n) {}
 
 inline void
-TextView::init_delimiter_set(std::string_view const &delimiters, std::bitset<256> &set)
-{
+TextView::init_delimiter_set(std::string_view const &delimiters, std::bitset<256> &set) {
   set.reset();
   for (char c : delimiters)
     set[static_cast<uint8_t>(c)] = true;
 }
 
 inline TextView &
-TextView::clear()
-{
+TextView::clear() {
   new (this) self_type();
   return *this;
 }
 
-inline char TextView::operator*() const
-{
+inline char TextView::operator*() const {
   return this->empty() ? char(0) : *(this->data());
 }
 
-inline bool TextView::operator!() const
-{
+inline bool TextView::operator!() const {
   return this->empty();
 }
 
-inline TextView::operator bool() const
-{
+inline TextView::operator bool() const {
   return !this->empty();
 }
 
 inline TextView &
-TextView::operator++()
-{
+TextView::operator++() {
   this->remove_prefix(1);
   return *this;
 }
 
 inline TextView
-TextView::operator++(int)
-{
+TextView::operator++(int) {
   self_type zret{*this};
   this->remove_prefix(1);
   return zret;
 }
 
 inline TextView &
-TextView::operator+=(size_t n)
-{
+TextView::operator+=(size_t n) {
   this->remove_prefix(n);
   return *this;
 }
 
 template <size_t N>
 inline TextView::self_type &
-TextView::operator=(const char (&s)[N])
-{
+TextView::operator=(const char (&s)[N]) {
   return *this = self_type{s, s[N - 1] ? N : N - 1};
 }
 
 inline TextView &
-TextView::operator=(super_type const &that)
-{
+TextView::operator=(super_type const &that) {
   this->super_type::operator=(that);
   return *this;
 }
 
 inline TextView &
-TextView::operator=(const std::string &s)
-{
+TextView::operator=(const std::string &s) {
   this->super_type::operator=(s);
   return *this;
 }
 
 inline TextView &
-TextView::assign(const std::string &s)
-{
+TextView::assign(const std::string &s) {
   *this = super_type(s);
   return *this;
 }
 
 inline TextView &
-TextView::assign(char const *ptr, size_t n)
-{
+TextView::assign(char const *ptr, size_t n) {
   *this = super_type(ptr, n);
   return *this;
 }
 
 inline TextView &
-TextView::assign(char const *b, char const *e)
-{
+TextView::assign(char const *b, char const *e) {
   *this = super_type(b, e - b);
   return *this;
 }
 
 inline TextView
-TextView::prefix(size_t n) const
-{
+TextView::prefix(size_t n) const {
   return {this->data(), std::min(n, this->size())};
 }
 
 inline TextView
-TextView::prefix(int n) const
-{
+TextView::prefix(int n) const {
   return {this->data(), std::min<size_t>(n, this->size())};
 }
 
 inline TextView
-TextView::prefix_at(char c) const
-{
+TextView::prefix_at(char c) const {
   self_type zret; // default to empty return.
-  if (auto n = this->find(c); n != npos) {
+  if (auto n = this->find(c); n != npos)
+  {
     zret.assign(this->data(), n);
   }
   return zret;
 }
 
 inline TextView
-TextView::prefix_at(std::string_view const &delimiters) const
-{
+TextView::prefix_at(std::string_view const &delimiters) const {
   self_type zret; // default to empty return.
-  if (auto n = this->find_first_of(delimiters); n != npos) {
+  if (auto n = this->find_first_of(delimiters); n != npos)
+  {
     zret.assign(this->data(), n);
   }
   return zret;
@@ -978,35 +950,34 @@ TextView::prefix_at(std::string_view const &delimiters) const
 
 template <typename F>
 TextView::self_type
-TextView::prefix_if(F const &pred) const
-{
+TextView::prefix_if(F const &pred) const {
   self_type zret; // default to empty return.
-  if (auto n = this->find_if(pred); n != npos) {
+  if (auto n = this->find_if(pred); n != npos)
+  {
     zret.assign(this->data(), n);
   }
   return zret;
 }
 
 inline auto
-TextView::remove_prefix(size_t n) -> self_type &
-{
+TextView::remove_prefix(size_t n) -> self_type & {
   this->super_type::remove_prefix(std::min(n, this->size()));
   return *this;
 }
 
 inline TextView &
-TextView::remove_prefix_at(char c)
-{
-  if (auto n = this->find(c); n != npos) {
+TextView::remove_prefix_at(char c) {
+  if (auto n = this->find(c); n != npos)
+  {
     this->super_type::remove_prefix(n + 1);
   }
   return *this;
 }
 
 inline TextView &
-TextView::remove_prefix_at(std::string_view const &delimiters)
-{
-  if (auto n = this->find_first_of(delimiters); n != npos) {
+TextView::remove_prefix_at(std::string_view const &delimiters) {
+  if (auto n = this->find_first_of(delimiters); n != npos)
+  {
     this->super_type::remove_prefix(n + 1);
   }
   return *this;
@@ -1014,19 +985,19 @@ TextView::remove_prefix_at(std::string_view const &delimiters)
 
 template <typename F>
 TextView::self_type &
-TextView::remove_prefix_if(F const &pred)
-{
-  if (auto n = this->find_if(pred); n != npos) {
+TextView::remove_prefix_if(F const &pred) {
+  if (auto n = this->find_if(pred); n != npos)
+  {
     this->super_type::remove_prefix(n + 1);
   }
   return *this;
 }
 
 inline TextView
-TextView::split_prefix(size_t n)
-{
+TextView::split_prefix(size_t n) {
   self_type zret; // default to empty return.
-  if (n < this->size()) {
+  if (n < this->size())
+  {
     zret = this->prefix(n);
     this->remove_prefix(std::min(n + 1, this->size()));
   }
@@ -1034,33 +1005,28 @@ TextView::split_prefix(size_t n)
 }
 
 inline TextView
-TextView::split_prefix(int n)
-{
+TextView::split_prefix(int n) {
   return this->split_prefix(size_t(n));
 }
 
 inline TextView
-TextView::split_prefix_at(char c)
-{
+TextView::split_prefix_at(char c) {
   return this->split_prefix(this->find(c));
 }
 
 inline TextView
-TextView::split_prefix_at(std::string_view const &delimiters)
-{
+TextView::split_prefix_at(std::string_view const &delimiters) {
   return this->split_prefix(this->find_first_of(delimiters));
 }
 
 template <typename F>
 TextView::self_type
-TextView::split_prefix_if(F const &pred)
-{
+TextView::split_prefix_if(F const &pred) {
   return this->split_prefix(this->find_if(pred));
 }
 
 inline TextView
-TextView::take_prefix(size_t n)
-{
+TextView::take_prefix(size_t n) {
   n              = std::min(n, this->size());
   self_type zret = this->prefix(n);
   this->remove_prefix(std::min(n + 1, this->size()));
@@ -1068,42 +1034,37 @@ TextView::take_prefix(size_t n)
 }
 
 inline TextView
-TextView::take_prefix_at(char c)
-{
+TextView::take_prefix_at(char c) {
   return this->take_prefix(this->find(c));
 }
 
 inline TextView
-TextView::take_prefix_at(std::string_view const &delimiters)
-{
+TextView::take_prefix_at(std::string_view const &delimiters) {
   return this->take_prefix(this->find_first_of(delimiters));
 }
 
 template <typename F>
 TextView::self_type
-TextView::take_prefix_if(F const &pred)
-{
+TextView::take_prefix_if(F const &pred) {
   return this->take_prefix(this->find_if(pred));
 }
 
 inline TextView
-TextView::suffix(size_t n) const
-{
+TextView::suffix(size_t n) const {
   n = std::min(n, this->size());
   return {this->data_end() - n, n};
 }
 
 inline TextView
-TextView::suffix(int n) const
-{
+TextView::suffix(int n) const {
   return this->suffix(size_t(n));
 }
 
 inline TextView
-TextView::suffix_at(char c) const
-{
+TextView::suffix_at(char c) const {
   self_type zret;
-  if (auto n = this->rfind(c); n != npos) {
+  if (auto n = this->rfind(c); n != npos)
+  {
     ++n;
     zret.assign(this->data() + n, this->size() - n);
   }
@@ -1111,10 +1072,10 @@ TextView::suffix_at(char c) const
 }
 
 inline TextView
-TextView::suffix_at(std::string_view const &delimiters) const
-{
+TextView::suffix_at(std::string_view const &delimiters) const {
   self_type zret;
-  if (auto n = this->find_last_of(delimiters); n != npos) {
+  if (auto n = this->find_last_of(delimiters); n != npos)
+  {
     ++n;
     zret.assign(this->data() + n, this->size() - n);
   }
@@ -1123,10 +1084,10 @@ TextView::suffix_at(std::string_view const &delimiters) const
 
 template <typename F>
 TextView::self_type
-TextView::suffix_if(F const &pred) const
-{
+TextView::suffix_if(F const &pred) const {
   self_type zret;
-  if (auto n = this->rfind_if(pred); n != npos) {
+  if (auto n = this->rfind_if(pred); n != npos)
+  {
     ++n;
     zret.assign(this->data() + n, this->size() - n);
   }
@@ -1134,25 +1095,24 @@ TextView::suffix_if(F const &pred) const
 }
 
 inline auto
-TextView::remove_suffix(size_t n) -> self_type &
-{
+TextView::remove_suffix(size_t n) -> self_type & {
   this->super_type::remove_suffix(std::min(n, this->size()));
   return *this;
 }
 
 inline TextView &
-TextView::remove_suffix_at(char c)
-{
-  if (auto n = this->rfind(c); n != npos) {
+TextView::remove_suffix_at(char c) {
+  if (auto n = this->rfind(c); n != npos)
+  {
     this->remove_suffix(this->size() - n);
   }
   return *this;
 }
 
 inline TextView &
-TextView::remove_suffix_at(std::string_view const &delimiters)
-{
-  if (auto n = this->find_last_of(delimiters); n != npos) {
+TextView::remove_suffix_at(std::string_view const &delimiters) {
+  if (auto n = this->find_last_of(delimiters); n != npos)
+  {
     this->remove_suffix(this->size() - n);
   }
   return *this;
@@ -1160,17 +1120,16 @@ TextView::remove_suffix_at(std::string_view const &delimiters)
 
 template <typename F>
 TextView::self_type &
-TextView::remove_suffix_if(F const &pred)
-{
-  if (auto n = this->rfind_if(pred); n != npos) {
+TextView::remove_suffix_if(F const &pred) {
+  if (auto n = this->rfind_if(pred); n != npos)
+  {
     this->remove_suffix(this->size() - n);
   }
   return *this;
 }
 
 inline TextView
-TextView::split_suffix(size_t n)
-{
+TextView::split_suffix(size_t n) {
   self_type zret;
   n    = std::min(n, this->size());
   zret = this->suffix(n);
@@ -1179,69 +1138,59 @@ TextView::split_suffix(size_t n)
 }
 
 inline auto
-TextView::split_suffix(int n) -> self_type
-{
+TextView::split_suffix(int n) -> self_type {
   return this->split_suffix(size_t(n));
 }
 
 inline TextView
-TextView::split_suffix_at(char c)
-{
+TextView::split_suffix_at(char c) {
   auto idx = this->rfind(c);
   return npos == idx ? self_type{} : this->split_suffix(this->size() - (idx + 1));
 }
 
 inline auto
-TextView::split_suffix_at(std::string_view const &delimiters) -> self_type
-{
+TextView::split_suffix_at(std::string_view const &delimiters) -> self_type {
   auto idx = this->find_last_of(delimiters);
   return npos == idx ? self_type{} : this->split_suffix(this->size() - (idx + 1));
 }
 
 template <typename F>
 TextView::self_type
-TextView::split_suffix_if(F const &pred)
-{
+TextView::split_suffix_if(F const &pred) {
   return this->split_suffix(this->rfind_if(pred));
 }
 
 inline TextView
-TextView::take_suffix(size_t n)
-{
+TextView::take_suffix(size_t n) {
   self_type zret{*this};
   *this = zret.split_prefix(n);
   return zret;
 }
 
 inline TextView
-TextView::take_suffix(int n)
-{
+TextView::take_suffix(int n) {
   return this->take_suffix(size_t(n));
 }
 
 inline TextView
-TextView::take_suffix_at(char c)
-{
+TextView::take_suffix_at(char c) {
   return this->take_suffix(this->rfind(c));
 }
 
 inline TextView
-TextView::take_suffix_at(std::string_view const &delimiters)
-{
+TextView::take_suffix_at(std::string_view const &delimiters) {
   return this->take_suffix(this->find_last_of(delimiters));
 }
 
 template <typename F>
 TextView::self_type
-TextView::take_suffix_if(F const &pred)
-{
+TextView::take_suffix_if(F const &pred) {
   return this->take_suffix_at(this->rfind_if(pred));
 }
 
 template <typename F>
 inline size_t
-TextView::find_if(F const &pred) const
-{
+TextView::find_if(F const &pred) const {
   for (const char *spot = this->data(), *limit = this->data_end(); spot < limit; ++spot)
     if (pred(*spot))
       return spot - this->data();
@@ -1250,8 +1199,7 @@ TextView::find_if(F const &pred) const
 
 template <typename F>
 inline size_t
-TextView::rfind_if(F const &pred) const
-{
+TextView::rfind_if(F const &pred) const {
   for (const char *spot = this->data_end(), *limit = this->data(); spot > limit;)
     if (pred(*--spot))
       return spot - this->data();
@@ -1259,29 +1207,25 @@ TextView::rfind_if(F const &pred) const
 }
 
 inline TextView &
-TextView::ltrim(char c)
-{
+TextView::ltrim(char c) {
   this->remove_prefix(this->find_first_not_of(c));
   return *this;
 }
 
 inline TextView &
-TextView::rtrim(char c)
-{
+TextView::rtrim(char c) {
   auto n = this->find_last_not_of(c);
   this->remove_suffix(this->size() - (n == npos ? 0 : n + 1));
   return *this;
 }
 
 inline TextView &
-TextView::trim(char c)
-{
+TextView::trim(char c) {
   return this->ltrim(c).rtrim(c);
 }
 
 inline TextView &
-TextView::ltrim(std::string_view const &delimiters)
-{
+TextView::ltrim(std::string_view const &delimiters) {
   std::bitset<256> valid;
   this->init_delimiter_set(delimiters, valid);
   const char *spot;
@@ -1295,14 +1239,12 @@ TextView::ltrim(std::string_view const &delimiters)
 }
 
 inline TextView &
-TextView::ltrim(const char *delimiters)
-{
+TextView::ltrim(const char *delimiters) {
   return this->ltrim(std::string_view(delimiters));
 }
 
 inline TextView &
-TextView::rtrim(std::string_view const &delimiters)
-{
+TextView::rtrim(std::string_view const &delimiters) {
   std::bitset<256> valid;
   this->init_delimiter_set(delimiters, valid);
   const char *spot  = this->data_end();
@@ -1316,8 +1258,7 @@ TextView::rtrim(std::string_view const &delimiters)
 }
 
 inline TextView &
-TextView::trim(std::string_view const &delimiters)
-{
+TextView::trim(std::string_view const &delimiters) {
   std::bitset<256> valid;
   this->init_delimiter_set(delimiters, valid);
   const char *spot;
@@ -1338,15 +1279,13 @@ TextView::trim(std::string_view const &delimiters)
 }
 
 inline TextView &
-TextView::trim(const char *delimiters)
-{
+TextView::trim(const char *delimiters) {
   return this->trim(std::string_view(delimiters));
 }
 
 template <typename F>
 TextView::self_type &
-TextView::ltrim_if(F const &pred)
-{
+TextView::ltrim_if(F const &pred) {
   const char *spot;
   const char *limit;
   for (spot = this->data(), limit = this->data_end(); spot < limit && pred(*spot); ++spot)
@@ -1357,8 +1296,7 @@ TextView::ltrim_if(F const &pred)
 
 template <typename F>
 TextView::self_type &
-TextView::rtrim_if(F const &pred)
-{
+TextView::rtrim_if(F const &pred) {
   const char *spot  = this->data_end();
   const char *limit = this->data();
   while (limit < spot-- && pred(*spot))
@@ -1369,21 +1307,19 @@ TextView::rtrim_if(F const &pred)
 
 template <typename F>
 TextView::self_type &
-TextView::trim_if(F const &pred)
-{
+TextView::trim_if(F const &pred) {
   return this->ltrim_if(pred).rtrim_if(pred);
 }
 
 inline char const *
-TextView::data_end() const
-{
+TextView::data_end() const {
   return this->data() + this->size();
 }
 
 inline TextView
-TextView::substr(size_type pos, size_type count) const
-{
-  if (pos >= this->size()) {
+TextView::substr(size_type pos, size_type count) const {
+  if (pos >= this->size())
+  {
     return {};
   }
   count = std::min(this->size() - pos, count);
@@ -1391,33 +1327,28 @@ TextView::substr(size_type pos, size_type count) const
 }
 
 inline bool
-TextView::starts_with(std::string_view const &prefix) const
-{
+TextView::starts_with(std::string_view const &prefix) const {
   return this->size() >= prefix.size() && 0 == ::memcmp(this->data(), prefix.data(), prefix.size());
 }
 
 inline bool
-TextView::starts_with_nocase(std::string_view const &prefix) const
-{
+TextView::starts_with_nocase(std::string_view const &prefix) const {
   return this->size() >= prefix.size() && 0 == ::strncasecmp(this->data(), prefix.data(), prefix.size());
 }
 
 inline bool
-TextView::ends_with(std::string_view const &suffix) const
-{
+TextView::ends_with(std::string_view const &suffix) const {
   return this->size() >= suffix.size() && 0 == ::memcmp(this->data_end() - suffix.size(), suffix.data(), suffix.size());
 }
 
 inline bool
-TextView::ends_with_nocase(std::string_view const &suffix) const
-{
+TextView::ends_with_nocase(std::string_view const &suffix) const {
   return this->size() >= suffix.size() && 0 == ::strncasecmp(this->data_end() - suffix.size(), suffix.data(), suffix.size());
 }
 
 template <typename Stream>
 Stream &
-TextView::stream_write(Stream &os, const TextView &b) const
-{
+TextView::stream_write(Stream &os, const TextView &b) const {
   // Local function, avoids extra template work.
   static const auto stream_fill = [](Stream &os, size_t n) -> Stream & {
     static constexpr size_t pad_size = 8;
@@ -1432,9 +1363,11 @@ TextView::stream_write(Stream &os, const TextView &b) const
   };
 
   const std::size_t w = os.width();
-  if (w <= b.size()) {
+  if (w <= b.size())
+  {
     os.write(b.data(), b.size());
-  } else {
+  } else
+  {
     const std::size_t pad_size = w - b.size();
     const bool align_left      = (os.flags() & Stream::adjustfield) == Stream::left;
     if (!align_left && os.good())
@@ -1473,8 +1406,7 @@ extern template std::ostream &TextView::stream_write(std::ostream &, const TextV
  * auto xv = transform_view_of(&tolower, source);
  * @endcode
  */
-template <typename X, typename V> class TransformView
-{
+template <typename X, typename V> class TransformView {
   using self_type = TransformView; ///< Self reference type.
   using iter      = decltype(static_cast<V *>(nullptr)->begin());
 
@@ -1534,32 +1466,26 @@ protected:
 };
 
 template <typename X, typename V>
-TransformView<X, V>::TransformView(transform_type &&xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end())
-{
-}
+TransformView<X, V>::TransformView(transform_type &&xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end()) {}
 
 template <typename X, typename V>
-TransformView<X, V>::TransformView(transform_type const &xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end())
-{
-}
+TransformView<X, V>::TransformView(transform_type const &xf, source_view_type const &v)
+  : _xf(xf), _spot(v.begin()), _limit(v.end()) {}
 
-template <typename X, typename V> auto TransformView<X, V>::operator*() const -> value_type
-{
+template <typename X, typename V> auto TransformView<X, V>::operator*() const -> value_type {
   return _xf(*_spot);
 }
 
 template <typename X, typename V>
 auto
-TransformView<X, V>::operator++() -> self_type &
-{
+TransformView<X, V>::operator++() -> self_type & {
   ++_spot;
   return *this;
 }
 
 template <typename X, typename V>
 auto
-TransformView<X, V>::operator++(int) -> self_type
-{
+TransformView<X, V>::operator++(int) -> self_type {
   self_type zret{*this};
   ++_spot;
   return zret;
@@ -1567,27 +1493,23 @@ TransformView<X, V>::operator++(int) -> self_type
 
 template <typename X, typename V>
 bool
-TransformView<X, V>::empty() const
-{
+TransformView<X, V>::empty() const {
   return _spot == _limit;
 }
 
-template <typename X, typename V> TransformView<X, V>::operator bool() const
-{
+template <typename X, typename V> TransformView<X, V>::operator bool() const {
   return _spot != _limit;
 }
 
 template <typename X, typename V>
 bool
-TransformView<X, V>::operator==(self_type const &that) const
-{
+TransformView<X, V>::operator==(self_type const &that) const {
   return _spot == that._spot && _limit == that._limit;
 }
 
 template <typename X, typename V>
 bool
-TransformView<X, V>::operator!=(self_type const &that) const
-{
+TransformView<X, V>::operator!=(self_type const &that) const {
   return _spot != that._spot || _limit != that._limit;
 }
 
@@ -1601,8 +1523,7 @@ TransformView<X, V>::operator!=(self_type const &that) const
  */
 template <typename X, typename V>
 TransformView<X, V>
-transform_view_of(X const &xf, V const &src)
-{
+transform_view_of(X const &xf, V const &src) {
   return TransformView<X, V>(xf, src);
 }
 
@@ -1613,8 +1534,7 @@ transform_view_of(X const &xf, V const &src)
  * This is a transform that returns the input unmodified. This is convenient when a transform is
  * required in general but not in in all cases.
  */
-template <typename V> class TransformView<void, V>
-{
+template <typename V> class TransformView<void, V> {
   using self_type = TransformView; ///< Self reference type.
   /// Iterator over source, for internal use.
   using iter = decltype(static_cast<V *>(nullptr)->begin());
@@ -1650,15 +1570,13 @@ public:
   value_type operator*() const { return *_spot; }
   /// Move to next element.
   self_type &
-  operator++()
-  {
+  operator++() {
     ++_spot;
     return *this;
   }
   /// Move to next element.
   self_type
-  operator++(int)
-  {
+  operator++(int) {
     auto zret{*this};
     ++*this;
     return zret;
@@ -1666,8 +1584,7 @@ public:
 
   /// Check if view is empty.
   bool
-  empty() const
-  {
+  empty() const {
     return _spot == _limit;
   }
   /// Check if bool is not empty.
@@ -1682,8 +1599,7 @@ protected:
 // Capture @c void transforms and make them identity transforms.
 template <typename V>
 TransformView<void, V>
-transform_view_of(V const &v)
-{
+transform_view_of(V const &v) {
   return TransformView<void, V>(v);
 }
 /// @endcond
diff --git a/swoc++/include/swoc/bwf_ex.h b/swoc++/include/swoc/bwf_ex.h
index 40f8a1c..1638a1d 100644
--- a/swoc++/include/swoc/bwf_ex.h
+++ b/swoc++/include/swoc/bwf_ex.h
@@ -68,17 +68,18 @@ namespace bwf
   namespace detail
   {
     // Special case conversions - these handle nullptr because the @c std::string_view spec is stupid.
-    inline std::string_view FirstOfConverter(std::nullptr_t) { return std::string_view{}; }
     inline std::string_view
-    FirstOfConverter(char const *s)
-    {
+    FirstOfConverter(std::nullptr_t) {
+      return std::string_view{};
+    }
+    inline std::string_view
+    FirstOfConverter(char const *s) {
       return std::string_view{s ? s : ""};
     }
     // Otherwise do any compliant conversion.
     template <typename T>
     std::string_view
-    FirstOfConverter(T &&t)
-    {
+    FirstOfConverter(T &&t) {
       return t;
     }
   } // namespace detail
@@ -87,10 +88,10 @@ namespace bwf
   /// All arguments must be convertible to @c std::string.
   template <typename... Args>
   std::string_view
-  FirstOf(Args &&... args)
-  {
+  FirstOf(Args &&... args) {
     std::array<std::string_view, sizeof...(args)> strings{{detail::FirstOfConverter(args)...}};
-    for (auto &s : strings) {
+    for (auto &s : strings)
+    {
       if (!s.empty())
         return s;
     }
@@ -142,8 +143,7 @@ namespace bwf
    */
   template <typename... Args>
   SubText<Args...>
-  If(bool flag, TextView const &fmt, Args &&... args)
-  {
+  If(bool flag, TextView const &fmt, Args &&... args) {
     return SubText<Args...>(flag ? fmt : TextView{}, std::forward_as_tuple(args...));
   }
 
@@ -151,20 +151,22 @@ namespace bwf
   {
     // @a T has the @c empty() method.
     template <typename T>
-    auto Optional(meta::CaseTag<2>, TextView fmt, T &&t) -> decltype(void(t.empty()), meta::TypeFunc<SubText<T>>())
-    {
+    auto
+    Optional(meta::CaseTag<2>, TextView fmt, T &&t) -> decltype(void(t.empty()), meta::TypeFunc<SubText<T>>()) {
       return SubText<T>(t.empty() ? TextView{} : fmt, std::forward_as_tuple(t));
     }
 
     // @a T is convertible to @c bool.
-    template <typename T> auto Optional(meta::CaseTag<1>, TextView fmt, T &&t) -> decltype(bool(t), meta::TypeFunc<SubText<T>>())
-    {
+    template <typename T>
+    auto
+    Optional(meta::CaseTag<1>, TextView fmt, T &&t) -> decltype(bool(t), meta::TypeFunc<SubText<T>>()) {
       return SubText<T>(bool(t) ? fmt : TextView{}, std::forward_as_tuple(t));
     }
 
     // @a T is not optional, always print.
-    template <typename T> auto Optional(meta::CaseTag<0>, TextView fmt, T &&t) -> SubText<T>
-    {
+    template <typename T>
+    auto
+    Optional(meta::CaseTag<0>, TextView fmt, T &&t) -> SubText<T> {
       return SubText<T>(fmt, std::forward_as_tuple(t));
     }
   } // namespace detail
@@ -202,8 +204,7 @@ namespace bwf
    */
   template <typename ARG>
   SubText<ARG>
-  Optional(TextView fmt, ARG &&arg)
-  {
+  Optional(TextView fmt, ARG &&arg) {
     return detail::Optional(meta::CaseArg, fmt, std::forward<ARG>(arg));
   }
 } // namespace bwf
@@ -214,9 +215,9 @@ BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &
 
 template <typename... Args>
 BufferWriter &
-bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::SubText<Args...> const &subtext)
-{
-  if (!subtext._fmt.empty()) {
+bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::SubText<Args...> const &subtext) {
+  if (!subtext._fmt.empty())
+  {
     w.print_v(subtext._fmt, subtext._args);
   }
   return w;
diff --git a/swoc++/include/swoc/bwf_ip.h b/swoc++/include/swoc/bwf_ip.h
index 0ea19b1..be1fa46 100644
--- a/swoc++/include/swoc/bwf_ip.h
+++ b/swoc++/include/swoc/bwf_ip.h
@@ -26,15 +26,20 @@
 
 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, in_addr const &addr);
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, in_addr_t addr);
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, in6_addr const &addr);
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IPAddr const &addr);
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr);
+
 inline BufferWriter &
-bwformat(BufferWriter &w, bwf::Spec const &spec, IPEndpoint const &addr)
-{
+bwformat(BufferWriter &w, bwf::Spec const &spec, IPEndpoint const &addr) {
   return bwformat(w, spec, &addr.sa);
 }
 
+inline BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IP4Addr const& addr) {
+  return bwformat(w, spec, addr.network_order());
+}
+
 } // namespace swoc
diff --git a/swoc++/include/swoc/bwf_std.h b/swoc++/include/swoc/bwf_std.h
index 0c3065f..d6b092a 100644
--- a/swoc++/include/swoc/bwf_std.h
+++ b/swoc++/include/swoc/bwf_std.h
@@ -31,8 +31,7 @@ namespace std
 {
 template <typename T>
 swoc::BufferWriter &
-bwformat(swoc::BufferWriter &w, swoc::bwf::Spec const &spec, atomic<T> const &v)
-{
+bwformat(swoc::BufferWriter &w, swoc::bwf::Spec const &spec, atomic<T> const &v) {
   return ::swoc::bwformat(w, spec, v.load());
 }
 
diff --git a/swoc++/include/swoc/swoc_file.h b/swoc++/include/swoc/swoc_file.h
index f3def54..e02c369 100644
--- a/swoc++/include/swoc/swoc_file.h
+++ b/swoc++/include/swoc/swoc_file.h
@@ -34,8 +34,7 @@ namespace file
 {
   /** Utility class for file system paths.
    */
-  class path
-  {
+  class path {
     using self_type = path;
 
   public:
@@ -105,8 +104,7 @@ namespace file
   };
 
   /// Information about a file.
-  class file_status
-  {
+  class file_status {
     using self_type = file_status;
 
   protected:
@@ -171,51 +169,43 @@ namespace file
   inline path::path(std::string &&that) : _path(std::move(that)) {}
 
   inline path &
-  path::operator=(std::string_view p)
-  {
+  path::operator=(std::string_view p) {
     _path.assign(p);
     return *this;
   }
 
   inline char const *
-  path::c_str() const
-  {
+  path::c_str() const {
     return _path.c_str();
   }
 
   inline std::string const &
-  path::string() const
-  {
+  path::string() const {
     return _path;
   }
 
   inline swoc::TextView
-  path::view() const
-  {
+  path::view() const {
     return _path;
   }
 
   inline bool
-  path::empty() const
-  {
+  path::empty() const {
     return _path.empty();
   }
 
   inline bool
-  path::is_absolute() const
-  {
+  path::is_absolute() const {
     return !_path.empty() && '/' == _path[0];
   }
 
   inline bool
-  path::is_relative() const
-  {
+  path::is_relative() const {
     return !this->is_absolute();
   }
 
   inline path &
-  path::operator/=(const self_type &that)
-  {
+  path::operator/=(const self_type &that) {
     return *this /= std::string_view(that._path);
   }
 
@@ -224,26 +214,22 @@ namespace file
        @return A @c path with the combined path.
   */
   inline path
-  operator/(const path &lhs, const path &rhs)
-  {
+  operator/(const path &lhs, const path &rhs) {
     return path(lhs) /= rhs;
   }
 
   inline path
-  operator/(path &&lhs, const path &rhs)
-  {
+  operator/(path &&lhs, const path &rhs) {
     return path(std::move(lhs)) /= rhs;
   }
 
   inline path
-  operator/(const path &lhs, std::string_view rhs)
-  {
+  operator/(const path &lhs, std::string_view rhs) {
     return path(lhs) /= rhs;
   }
 
   inline path
-  operator/(path &&lhs, std::string_view rhs)
-  {
+  operator/(path &&lhs, std::string_view rhs) {
     return path(std::move(lhs)) /= rhs;
   }
 
diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h
index dccf0e7..2ba59dd 100644
--- a/swoc++/include/swoc/swoc_ip.h
+++ b/swoc++/include/swoc/swoc_ip.h
@@ -1,40 +1,29 @@
+#pragma once
+// SPDX-License-Identifier: Apache-2.0
 /** @file
-
-  IP address and network related classes.
-
-  @section license License
-
-  Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
-  See the NOTICE file distributed with this work for additional information regarding copyright
-  ownership.  The ASF licenses this file to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance with the License.  You may obtain a
-  copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software distributed under the License
-  is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  or implied. See the License for the specific language governing permissions and limitations under
-  the License.
-
+   IP address and network related classes.
  */
 
-#pragma once
-
 #include <netinet/in.h>
 #include <string_view>
+#include <variant>
 
+#include <swoc/DiscreteRange.h>
+#include <swoc/RBTree.h>
 #include "bwf_base.h"
 
 namespace swoc
 {
 class IP4Addr;
 class IP6Addr;
-class IPAddr; // forward declare.
+class IPAddr;
+class IPMask;
 class IP4Range;
 class IP6Range;
 class IPRange;
 
+using ::std::string_view;
+
 /** A union to hold @c sockaddr compliant IP address structures.
 
     This class contains a number of static methods to perform operations on external @c sockaddr
@@ -54,7 +43,7 @@ union IPEndpoint {
   /// Default construct invalid instance.
   IPEndpoint();
   /// Construct from string representation of an address.
-  IPEndpoint(std::string_view const &text);
+  IPEndpoint(string_view const &text);
   // Construct from @a IPAddr
   IPEndpoint(IPAddr const &addr);
 
@@ -69,8 +58,7 @@ union IPEndpoint {
    * Any of the out parameters can be @c nullptr in which case they are not updated.
    * This parses and discards the IPv6 brackets.
    */
-  static bool tokenize(std::string_view src, std::string_view *host = nullptr, std::string_view *port = nullptr,
-                       std::string_view *rest = nullptr);
+  static bool tokenize(string_view src, string_view *host = nullptr, string_view *port = nullptr, string_view *rest = nullptr);
 
   /** Parse a string for an IP address.
 
@@ -79,7 +67,7 @@ union IPEndpoint {
 
       @return @c true on success, @c false otherwise.
   */
-  bool parse(std::string_view const &str);
+  bool parse(string_view const &str);
 
   /// Invalidate a @c sockaddr.
   static void invalidate(sockaddr *addr);
@@ -157,19 +145,21 @@ union IPEndpoint {
   operator sockaddr const *() const { return &sa; }
 
   /// The string name of the address family.
-  static std::string_view family_name(sa_family_t family);
+  static string_view family_name(sa_family_t family);
 };
 
 /** Storage for an IPv4 address.
     Stored in network order.
  */
-class IP4Addr
-{
+class IP4Addr {
   using self_type = IP4Addr; ///< Self reference type.
+  friend class IP4Range;
 public:
   static constexpr size_t SIZE = sizeof(in_addr_t); ///< Size of IPv4 address in bytes.
+  static const self_type MIN;
+  static const self_type MAX;
 
-  IP4Addr(); ///< Default constructor - invalid result.
+  constexpr IP4Addr() = default; ///< Default constructor - invalid result.
 
   /// Construct using IPv4 @a addr.
   explicit constexpr IP4Addr(in_addr_t addr);
@@ -177,23 +167,32 @@ public:
   explicit IP4Addr(sockaddr_in const *addr);
   /// Construct from text representation.
   /// If the @a text is invalid the result is an invalid instance.
-  explicit IP4Addr(std::string_view const &text);
+  IP4Addr(string_view const &text);
 
   /// 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++();
+
+  self_type &operator--();
+
+  self_type &operator&=(IPMask const& mask);
+  self_type &operator|=(IPMask const& mask);
+
   /// Write to @c sockaddr.
   sockaddr *fill(sockaddr_in *sa, in_port_t port = 0) const;
 
+  in_addr_t network_order() const;
+
   /** Parse @a text as IPv4 address.
       The address resulting from the parse is copied to this object if the conversion is successful,
       otherwise this object is invalidated.
 
       @return @c true on success, @c false otherwise.
   */
-  bool parse(std::string_view const &text);
+  bool load(string_view const &text);
 
   /// Standard ternary compare.
   int cmp(self_type const &that) const;
@@ -213,37 +212,61 @@ public:
 
 protected:
   friend bool operator==(self_type const &, self_type const &);
-  in_addr_t _addr;
+  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 &);
+
+  in_addr_t _addr = INADDR_ANY;
 };
 
 /** Storage for an IPv6 address.
     This should be presumed to be in network order.
  */
-class IP6Addr
-{
+class IP6Addr {
   using self_type = IP6Addr; ///< Self reference type.
+  friend class IP6Range;
 public:
   using QUAD                      = uint16_t;            ///< Size of one segment of an IPv6 address.
-  static constexpr size_t SIZE    = sizeof(in6_addr);    ///< Size of IPv4 address in bytes.
+  static constexpr size_t SIZE    = 16;    ///< Size of address in bytes.
+  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.
 
-  IP6Addr(); ///< Default constructor - invalid result.
+  /// Type for the actual address.
+  /// Equivalent to the data type for data member @c s6_addr in @c in6_addr.
+  using raw_type = std::array<unsigned char, SIZE>;
+  using quad_type = std::array<unsigned short, N_QUADS>;
+
+  static const self_type MIN;
+  static const self_type MAX;
+
+  IP6Addr() = default; ///< Default constructor - 0 address.
 
   /// Construct using IPv6 @a addr.
-  explicit constexpr IP6Addr(in6_addr addr);
+  explicit IP6Addr(in6_addr const & addr);
   /// Construct from @c sockaddr_in.
-  explicit IP6Addr(sockaddr_in6 const *addr);
+  explicit IP6Addr(sockaddr_in6 const *addr) {
+    *this = addr;
+  }
   /// Construct from text representation.
   /// If the @a text is invalid the result is an invalid instance.
-  explicit IP6Addr(std::string_view text);
+  IP6Addr(string_view const& text);
 
-  /// Assign from IPv4 raw address.
-  self_type &operator=(in6_addr ip);
+  self_type &operator++();
+
+  self_type &operator--();
+
+  /// Assign from IPv6 raw address.
+  self_type &operator=(in6_addr const& ip);
   /// Set to the address in @a addr.
   self_type &operator=(sockaddr_in6 const *addr);
 
-  /// Write to @c sockaddr.
-  sockaddr *fill(sockaddr *sa, in_port_t port = 0) const;
+  /// Write to @c sockaddr using network order and @a port.
+  sockaddr *copy_to(sockaddr *sa, in_port_t port = 0) const;
+
+  /// Copy address to @a addr in network order.
+  in6_addr & copy_to(in6_addr & addr) const;
+
+  in6_addr network_order() const;
 
   /** Parse a string for an IP address.
 
@@ -252,7 +275,7 @@ public:
 
       @return @c true on success, @c false otherwise.
   */
-  bool parse(std::string_view const &str);
+  bool load(string_view const &str);
 
   /// Generic compare.
   int cmp(self_type const &that) const;
@@ -261,40 +284,61 @@ public:
   /// @return The address family.
   sa_family_t family() const;
 
-  operator in6_addr const &() const;
-
   /// Test for multicast
   bool is_multicast() const;
 
   /// Test for loopback
   bool is_loopback() const;
 
+  self_type & clear() {
+    _addr._u64[0] = _addr._u64[1] = 0;
+    return *this;
+  }
+
+  static void reorder(in6_addr & dst, raw_type const & src);
+  static void reorder(raw_type & dst, in6_addr const& src);
+  static void reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]);
+
 protected:
   friend bool operator==(self_type const &, self_type const &);
-  in6_addr _addr;
+  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 &);
+  union {
+    uint64_t _u64[2] = {0, 0}; ///< 0 is MSW, 1 is LSW.
+    quad_type _quad; ///< By quad.
+    raw_type _raw; ///< By byte.
+  } _addr;
+
+  static constexpr std::array<unsigned, N_QUADS> QUAD_IDX = { 3,2,1,0,7,6,5,4 };
+
+  IP6Addr(uint64_t msw, uint64_t lsw) : _addr{msw, lsw} {}
 };
 
 /** Storage for an IP address.
-    This should be presumed to be in network order.
  */
-class IPAddr
-{
+class IPAddr {
   friend class IPRange;
   using self_type = IPAddr; ///< Self reference type.
 public:
   IPAddr() = default; ///< Default constructor - invalid result.
+  IPAddr(self_type const& that) = default; ///< Copy constructor.
 
   /// Construct using IPv4 @a addr.
-  explicit constexpr IPAddr(in_addr_t addr);
+  explicit IPAddr(in_addr_t addr);
+  /// Construct using an IPv4 @a addr
+  IPAddr(IP4Addr const &addr) : _family(AF_INET), _addr{addr} {}
   /// Construct using IPv6 @a addr.
-  explicit constexpr IPAddr(in6_addr const &addr);
+  explicit IPAddr(in6_addr const &addr);
+  /// construct using an IPv6 @a addr
+  IPAddr(IP6Addr const &addr) : _family(AF_INET6), _addr{addr} {}
   /// Construct from @c sockaddr.
   explicit IPAddr(sockaddr const *addr);
   /// Construct from @c IPEndpoint.
   explicit IPAddr(IPEndpoint const &addr);
   /// Construct from text representation.
   /// If the @a text is invalid the result is an invalid instance.
-  explicit IPAddr(std::string_view text);
+  explicit IPAddr(string_view text);
 
   /// Set to the address in @a addr.
   self_type &assign(sockaddr const *addr);
@@ -316,17 +360,18 @@ public:
   /// Assign from @c sockaddr
   self_type &operator=(sockaddr const *addr);
 
+  self_type &operator&=(IPMask const& mask);
+  self_type &operator|=(IPMask const& mask);
+
   /// Write to @c sockaddr.
   sockaddr *fill(sockaddr *sa, in_port_t port = 0) const;
 
-  /** Parse a string for an IP address.
-
-      The address resulting from the parse is copied to this object if the conversion is successful,
-      otherwise this object is invalidated.
-
-      @return @c true on success, @c false otherwise.
-  */
-  bool parse(std::string_view const &str);
+  /** Parse a string and load the result in @a this.
+   *
+   * @param text Text to parse.
+   * @return  @c true on success, @c false otherwise.
+   */
+  bool load(string_view const &text);
 
   /// Generic compare.
   int cmp(self_type const &that) const;
@@ -343,10 +388,8 @@ public:
   /// Test for IPv6.
   bool is_ip6() const;
 
-  in_addr_t raw_ip4() const;
-  in6_addr const &raw_ip6() const;
-  uint8_t const *raw_octet() const;
-  uint64_t const *raw_64() const;
+  in_addr_t network_ip4() const;
+  in6_addr network_ip6() const;
 
   /// Test for validity.
   bool is_valid() const;
@@ -366,80 +409,38 @@ public:
 protected:
   friend bool operator==(self_type const &, self_type const &);
 
-  sa_family_t _family{AF_UNSPEC}; ///< Protocol family.
-
   /// Address data.
   union raw_addr_type {
-    IP4Addr _ip4;
-    IP6Addr _ip6;                                    ///< IPv6 address storage.
+    IP4Addr _ip4; ///< IPv4 address (host)
+    IP6Addr _ip6;                                    ///< IPv6 address (host)
     uint8_t _octet[IP6Addr::SIZE];                   ///< IPv4 octets.
     uint64_t _u64[IP6Addr::SIZE / sizeof(uint64_t)]; ///< As 64 bit chunks.
 
-    // Constructors needed so @c IPAddr can have @c constexpr constructors.
     constexpr raw_addr_type();
-    constexpr raw_addr_type(in_addr_t addr);
-    constexpr raw_addr_type(in6_addr const &addr);
-  } _addr;
-};
-
-/** An inclusive range of IP addresses.
- *
- * Although this can handle IPv4 and IPv6, a specific range is always one or the other, a range
- * never spans address families.
- */
-class IPRange
-{
-  using self_type = IPRange;
-
-public:
-  IPRange();                       ///< Default (empty) range constructor.
-  IPRange(IPAddr min, IPAddr max); ///< Construct range.
+    raw_addr_type(in_addr_t addr) : _ip4(addr) {}
+    raw_addr_type(in6_addr const& addr) : _ip6(addr) {}
 
-  /** Construct range from text.
-   * This calls @c parse().
-   * @param text Range text.
-   * @see IPRange::parse
-   */
-  IPRange(std::string_view text);
-
-  /** Assign to this range from 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 parse(std::string_view text);
-
-  /// Reset to default state.
-  self_type &clear();
-
-  bool empty() const;
-
-  /// Minimum address in range.
-  IPAddr const &min() const;
-
-  /// Maximum address in range.
-  IPAddr const &max() const;
+    raw_addr_type(IP4Addr const& addr) : _ip4(addr) {}
+    raw_addr_type(IP6Addr const& addr) : _ip6(addr) {}
+  } _addr;
 
-protected:
-  IPAddr _min; ///< Minimum value in range (inclusive)
-  IPAddr _max; ///< Maximum value in range (inclusive)
+  sa_family_t _family{AF_UNSPEC}; ///< Protocol family.
 };
 
 /** An IP address mask.
  *
  * This is essentially a width for a bit mask.
  */
-class IpMask
-{
-  using self_type = IpMask;  ///< Self reference type.
+class IPMask {
+  using self_type = IPMask;  ///< Self reference type.
   using raw_type  = uint8_t; ///< Storage for mask width.
 
 public:
-  IpMask();
-  IpMask(raw_type count, sa_family_t family = AF_INET);
-  IpMask(std::string_view text);
+  IPMask() = default;
+  IPMask(raw_type count, sa_family_t family = AF_INET);
+  IPMask(string_view text);
+
+  bool load(string_view const& text);
 
   /** Get the CIDR mask wide enough to cover this address.
    * @param addr Input address.
@@ -462,22 +463,112 @@ private:
   sa_family_t _family{AF_UNSPEC};
 };
 
+
+/** An inclusive range of IPv4 addresses.
+ */
+class IP4Range : public DiscreteRange<IP4Addr> {
+  using self_type  = IP4Range;
+  using super_type = DiscreteRange<IP4Addr>;
+  using metric_type = IP4Addr;
+
+public:
+  using super_type::super_type; ///< Import super class constructors.
+
+  IP4Range() = default;
+  IP4Range(IP4Addr const& addr, IPMask const& mask);
+
+  /** Construct range from @a text.
+   *
+   * @param text Range text.
+   * @see IP4Range::load
+   *
+   * 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);
+  }
+
+  self_type & assign(IP4Addr const& addr, IPMask const& mask);
+
+  /** Assign to this range from 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);
+
+protected:
+};
+
+class IP6Range : public DiscreteRange<IP6Addr> {
+  using self_type  = IP6Range;
+  using super_type = DiscreteRange<IP6Addr>;
+
+public:
+  using super_type::super_type; ///< Import super class constructors.
+
+  self_type & assign(IP6Addr const& addr, IPMask const& mask);
+
+  bool load(string_view text);
+
+};
+
+class IPRange {
+  using self_type = IPRange;
+public:
+  IPRange() = default;
+
+  bool is(sa_family_t f) const {
+    return f == _family;
+  }
+
+  bool load(std::string_view const& text);
+
+  IPAddr min() const;
+  IPAddr max() const;
+
+  operator IP4Range & () { return _range._ip4; }
+  operator IP6Range & () { return _range._ip6; }
+  operator IP4Range const & () const { return _range._ip4; }
+  operator IP6Range const & () const { return _range._ip6; }
+
+protected:
+  /** Range container.
+   *
+   * @internal
+   *
+   * This was a @c std::variant at one point, but the complexity got in the way because
+   * - These objects have no state, need no destruction.
+   * - Construction was problematic because @c variant requires construction, then access,
+   *   whereas this needs access to construct (e.g. via the @c load method).
+   */
+  union {
+    std::monostate _nil; ///< Make constructor easier to implement.
+    IP4Range _ip4; ///< IPv4 range.
+    IP6Range _ip6; ///< IPv6 range.
+  } _range { std::monostate{} };
+  /// Family of @a _range.
+  sa_family_t _family { AF_UNSPEC };
+};
+
 /** Representation of an IP address network.
  *
  */
-class IpNet
-{
+class IpNet {
   using self_type = IpNet; ///< Self reference type.
 public:
   IpNet();
-  IpNet(const IPAddr &addr, const IpMask &mask);
+  IpNet(const IPAddr &addr, const IPMask &mask);
 
   operator IPAddr const &() const;
-  operator IpMask const &() const;
+  operator IPMask const &() const;
 
   IPAddr const &addr() const;
 
-  IpMask const &mask() const;
+  IPMask const &mask() const;
 
   IPAddr lower_bound() const;
   IPAddr upper_bound() const;
@@ -490,106 +581,184 @@ public:
   // Check if there are any addresses in both @a this and @a that.
   bool intersects(self_type const &that);
 
-  self_type &assign(IPAddr const &addr, IpMask const &mask);
+  self_type &assign(IPAddr const &addr, IPMask const &mask);
 
   static char const SEPARATOR; // the character used between the address and mask
 
-  operator std::string() const; // implicit
-  std::string ntoa() const;     // explicit
+  operator ::std::string() const; // implicit
+  ::std::string ntoa() const;     // explicit
   // the address width is per octet, the mask width for the bit count
-  std::string ntoa(int addr_width, int mask_width) const;
+  ::std::string ntoa(int addr_width, int mask_width) const;
 
-  static std::string ntoa(IpNet const &net); // DEPRECATED
-  static IpNet aton(std::string const &str); // DEPRECATED - use ctor
+  static ::std::string ntoa(IpNet const &net); // DEPRECATED
+  static IpNet aton(::std::string const &str); // DEPRECATED - use ctor
 
 protected:
   IPAddr _addr;
-  IpMask _mask;
+  IPMask _mask;
 };
 
+// --------------------------------------------------------------------------
+/** Coloring of IP address space.
+ *
+ * @tparam PAYLOAD The color class.
+ *
+ * This is a class to do fast coloring and lookup of the IP address space. It is range oriented and
+ * performs well for ranges, much less well for singletons. Conceptually every IP address is a key
+ * in the space and can have a color / payload of type @c PAYLOAD.
+ *
+ * @c PAYLOAD must have the properties
+ *
+ * - Cheap to copy.
+ * - Comparable via the equality and inequality operators.
+ */
+template <typename PAYLOAD> class IPSpace {
+  using self_type = IPSpace;
+  using IP4Space  = DiscreteSpace<IP4Addr, PAYLOAD>;
+  using IP6Space  = DiscreteSpace<IP6Addr, PAYLOAD>;
+
+public:
+  using payload_t = PAYLOAD; ///< Export payload type.
+
+  /// Construct an empty space.
+  IPSpace() = default;
+
+  /** Mark the range @a r with @a payload.
+   *
+   * @param r Range to mark.
+   * @param payload Payload to assign.
+   * @return @a this
+   *
+   * All addresses in @a r are set to have the @a payload.
+   */
+  self_type & mark(IP4Range const &r, PAYLOAD const &payload);
+
+  /** Blend @a payload in to the @a range.
+   *
+   * @tparam F Blending functor type (deduced).
+   * @param range Target range.
+   * @param payload Payload for @a range.
+   * @param blender Blending functor.
+   * @return @a this
+   *
+   * @a blender is required to have the signature <tt>void(PAYLOAD& lhs , PAYLOAD CONST&rhs)</tt>.
+   * It must act as a compound assignment operator, blending @a B into @a A. That is, if the
+   * result of blending B in to A is defined as "A @ B" for the binary operator "@", then
+   * @a blender computes "A @= B".
+   *
+   * Every address in @a range is assigned a payload. If the address does not already have a
+   * payload, it is assigned @a payload. If the address has a payload of A it is updated by
+   * invoking @a blender on the existing payload and @a payload.
+   */
+  template < typename F >
+  self_type & blend(IPRange const& range, PAYLOAD const& payload, F && blender);
+
+  template < typename F >
+  self_type & blend(IP4Range const& range, PAYLOAD const& payload, F && blender) {
+    _ip4.blend(range, payload, blender);
+    return *this;
+  }
+
+  template < typename F >
+  self_type & blend(IP6Range const& range, PAYLOAD const& payload, F && blender) {
+    _ip6.blend(range, payload, blender);
+    return *this;
+  }
+
+  /** 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(IP4Addr const &addr) {
+    return _ip4.find(addr);
+  }
+
+  /// @return The number of distinct ranges.
+  size_t count() const { return _ip4.count() + _ip6.count(); }
+
+  /// Remove all ranges.
+  void clear();
+
+  typename IP4Space::iterator begin() { return _ip4.begin(); }
+  typename IP4Space::iterator end() { return _ip4.end(); }
+
+protected:
+  IP4Space _ip4;
+  IP6Space _ip6;
+};
+// --------------------------------------------------------------------------
+
 // @c constexpr constructor is required to initialize _something_, it can't be completely uninitializing.
 inline constexpr IPAddr::raw_addr_type::raw_addr_type() : _ip4(INADDR_ANY) {}
-inline constexpr IPAddr::raw_addr_type::raw_addr_type(in_addr_t addr) : _ip4(addr) {}
-inline constexpr IPAddr::raw_addr_type::raw_addr_type(in6_addr const &addr) : _ip6(addr) {}
 
-inline constexpr IPAddr::IPAddr(in_addr_t addr) : _family(AF_INET), _addr(addr) {}
+inline IPAddr::IPAddr(in_addr_t addr) : _family(AF_INET), _addr(addr) {}
 
-inline constexpr IPAddr::IPAddr(in6_addr const &addr) : _family(AF_INET6), _addr(addr) {}
+inline IPAddr::IPAddr(in6_addr const &addr) : _family(AF_INET6), _addr(addr) {}
 
-inline IPAddr::IPAddr(sockaddr const *addr)
-{
+inline IPAddr::IPAddr(sockaddr const *addr) {
   this->assign(addr);
 }
 
-inline IPAddr::IPAddr(IPEndpoint const &addr)
-{
+inline IPAddr::IPAddr(IPEndpoint const &addr) {
   this->assign(&addr.sa);
 }
 
 inline IPAddr &
-IPAddr::operator=(in_addr_t addr)
-{
+IPAddr::operator=(in_addr_t addr) {
   _family    = AF_INET;
   _addr._ip4 = addr;
   return *this;
 }
 
 inline IPAddr &
-IPAddr::operator=(in6_addr const &addr)
-{
+IPAddr::operator=(in6_addr const &addr) {
   _family    = AF_INET6;
   _addr._ip6 = addr;
   return *this;
 }
 
 inline IPAddr &
-IPAddr::operator=(IPEndpoint const &addr)
-{
+IPAddr::operator=(IPEndpoint const &addr) {
   return this->assign(&addr.sa);
 }
 
 inline IPAddr &
-IPAddr::operator=(sockaddr const *addr)
-{
+IPAddr::operator=(sockaddr const *addr) {
   return this->assign(addr);
 }
 
 inline sa_family_t
-IPAddr::family() const
-{
+IPAddr::family() const {
   return _family;
 }
 
 inline bool
-IPAddr::is_ip4() const
-{
+IPAddr::is_ip4() const {
   return AF_INET == _family;
 }
 
 inline bool
-IPAddr::is_ip6() const
-{
+IPAddr::is_ip6() const {
   return AF_INET6 == _family;
 }
 
 inline bool
-IPAddr::isCompatibleWith(self_type const &that)
-{
+IPAddr::isCompatibleWith(self_type const &that) {
   return this->is_valid() && _family == that._family;
 }
 
 inline bool
-IPAddr::is_loopback() const
-{
+IPAddr::is_loopback() const {
   return (AF_INET == _family && 0x7F == _addr._octet[0]) || (AF_INET6 == _family && IN6_IS_ADDR_LOOPBACK(&_addr._ip6));
 }
 
 inline bool
-operator==(IPAddr const &lhs, IPAddr const &rhs)
-{
+operator==(IPAddr const &lhs, IPAddr const &rhs) {
   if (lhs._family != rhs._family)
     return false;
-  switch (lhs._family) {
+  switch (lhs._family)
+  {
   case AF_INET:
     return lhs._addr._ip4 == rhs._addr._ip4;
   case AF_INET6:
@@ -603,60 +772,53 @@ operator==(IPAddr const &lhs, IPAddr const &rhs)
 }
 
 inline bool
-operator!=(IPAddr const &lhs, IPAddr const &rhs)
-{
+operator!=(IPAddr const &lhs, IPAddr const &rhs) {
   return !(lhs == rhs);
 }
 
 inline IPAddr &
-IPAddr::assign(in_addr_t addr)
-{
+IPAddr::assign(in_addr_t addr) {
   _family    = AF_INET;
   _addr._ip4 = addr;
   return *this;
 }
 
 inline IPAddr &
-IPAddr::assign(in6_addr const &addr)
-{
+IPAddr::assign(in6_addr const &addr) {
   _family    = AF_INET6;
   _addr._ip6 = addr;
   return *this;
 }
 
 inline IPAddr &
-IPAddr::assign(sockaddr_in const *addr)
-{
-  if (addr) {
+IPAddr::assign(sockaddr_in const *addr) {
+  if (addr)
+  {
     _family    = AF_INET;
     _addr._ip4 = addr->sin_addr.s_addr;
-  } else {
-    _family = AF_UNSPEC;
-  }
+  } else
+  { _family = AF_UNSPEC; }
   return *this;
 }
 
 inline IPAddr &
-IPAddr::assign(sockaddr_in6 const *addr)
-{
-  if (addr) {
+IPAddr::assign(sockaddr_in6 const *addr) {
+  if (addr)
+  {
     _family    = AF_INET6;
     _addr._ip6 = addr->sin6_addr;
-  } else {
-    _family = AF_UNSPEC;
-  }
+  } else
+  { _family = AF_UNSPEC; }
   return *this;
 }
 
 inline bool
-IPAddr::is_valid() const
-{
+IPAddr::is_valid() const {
   return _family == AF_INET || _family == AF_INET6;
 }
 
 inline IPAddr &
-IPAddr::invalidate()
-{
+IPAddr::invalidate() {
   _family = AF_UNSPEC;
   return *this;
 }
@@ -664,181 +826,142 @@ IPAddr::invalidate()
 // Associated operators.
 bool operator==(IPAddr const &lhs, sockaddr const *rhs);
 inline bool
-operator==(sockaddr const *lhs, IPAddr const &rhs)
-{
+operator==(sockaddr const *lhs, IPAddr const &rhs) {
   return rhs == lhs;
 }
 inline bool
-operator!=(IPAddr const &lhs, sockaddr const *rhs)
-{
+operator!=(IPAddr const &lhs, sockaddr const *rhs) {
   return !(lhs == rhs);
 }
 inline bool
-operator!=(sockaddr const *lhs, IPAddr const &rhs)
-{
+operator!=(sockaddr const *lhs, IPAddr const &rhs) {
   return !(rhs == lhs);
 }
 inline bool
-operator==(IPAddr const &lhs, IPEndpoint const &rhs)
-{
+operator==(IPAddr const &lhs, IPEndpoint const &rhs) {
   return lhs == &rhs.sa;
 }
 inline bool
-operator==(IPEndpoint const &lhs, IPAddr const &rhs)
-{
+operator==(IPEndpoint const &lhs, IPAddr const &rhs) {
   return &lhs.sa == rhs;
 }
 inline bool
-operator!=(IPAddr const &lhs, IPEndpoint const &rhs)
-{
+operator!=(IPAddr const &lhs, IPEndpoint const &rhs) {
   return !(lhs == &rhs.sa);
 }
 inline bool
-operator!=(IPEndpoint const &lhs, IPAddr const &rhs)
-{
+operator!=(IPEndpoint const &lhs, IPAddr const &rhs) {
   return !(rhs == &lhs.sa);
 }
 
 inline bool
-operator<(IPAddr const &lhs, IPAddr const &rhs)
-{
+operator<(IPAddr const &lhs, IPAddr const &rhs) {
   return -1 == lhs.cmp(rhs);
 }
 
 inline bool
-operator>=(IPAddr const &lhs, IPAddr const &rhs)
-{
+operator>=(IPAddr const &lhs, IPAddr const &rhs) {
   return lhs.cmp(rhs) >= 0;
 }
 
 inline bool
-operator>(IPAddr const &lhs, IPAddr const &rhs)
-{
+operator>(IPAddr const &lhs, IPAddr const &rhs) {
   return 1 == lhs.cmp(rhs);
 }
 
 inline bool
-operator<=(IPAddr const &lhs, IPAddr const &rhs)
-{
+operator<=(IPAddr const &lhs, IPAddr const &rhs) {
   return lhs.cmp(rhs) <= 0;
 }
 
 inline in_addr_t
-IPAddr::raw_ip4() const
-{
-  return _addr._ip4;
-}
-
-inline in6_addr const &
-IPAddr::raw_ip6() const
-{
-  return _addr._ip6;
-}
-
-inline uint8_t const *
-IPAddr::raw_octet() const
-{
-  return _addr._octet;
+IPAddr::network_ip4() const {
+  return _addr._ip4.network_order();
 }
 
-inline uint64_t const *
-IPAddr::raw_64() const
-{
-  return _addr._u64;
+inline in6_addr
+IPAddr::network_ip6() const {
+  return _addr._ip6.network_order();
 }
 
 /// ------------------------------------------------------------------------------------
 
-inline IPEndpoint::IPEndpoint()
-{
+inline IPEndpoint::IPEndpoint() {
   sa.sa_family = AF_UNSPEC;
 }
 
-inline IPEndpoint::IPEndpoint(IPAddr const &addr)
-{
+inline IPEndpoint::IPEndpoint(IPAddr const &addr) {
   this->assign(addr);
 }
 
 inline IPEndpoint &
-IPEndpoint::invalidate()
-{
+IPEndpoint::invalidate() {
   sa.sa_family = AF_UNSPEC;
   return *this;
 }
 
 inline void
-IPEndpoint::invalidate(sockaddr *addr)
-{
+IPEndpoint::invalidate(sockaddr *addr) {
   addr->sa_family = AF_UNSPEC;
 }
 
 inline bool
-IPEndpoint::is_valid() const
-{
+IPEndpoint::is_valid() const {
   return sa.sa_family == AF_INET || sa.sa_family == AF_INET6;
 }
 
 inline IPEndpoint &
-IPEndpoint::operator=(self_type const &that)
-{
+IPEndpoint::operator=(self_type const &that) {
   self_type::assign(&sa, &that.sa);
   return *this;
 }
 
 inline IPEndpoint &
-IPEndpoint::assign(sockaddr const *src)
-{
+IPEndpoint::assign(sockaddr const *src) {
   self_type::assign(&sa, src);
   return *this;
 }
 
 inline IPEndpoint const &
-IPEndpoint::fill(sockaddr *addr) const
-{
+IPEndpoint::fill(sockaddr *addr) const {
   self_type::assign(addr, &sa);
   return *this;
 }
 
 inline bool
-IPEndpoint::is_ip4() const
-{
+IPEndpoint::is_ip4() const {
   return AF_INET == sa.sa_family;
 }
 
 inline bool
-IPEndpoint::is_ip6() const
-{
+IPEndpoint::is_ip6() const {
   return AF_INET6 == sa.sa_family;
 }
 
 inline sa_family_t
-IPEndpoint::family() const
-{
+IPEndpoint::family() const {
   return sa.sa_family;
 }
 
 inline in_port_t &
-IPEndpoint::port()
-{
+IPEndpoint::port() {
   return self_type::port(&sa);
 }
 
 inline in_port_t
-IPEndpoint::port() const
-{
+IPEndpoint::port() const {
   return self_type::port(&sa);
 }
 
 inline in_port_t
-IPEndpoint::host_order_port() const
-{
+IPEndpoint::host_order_port() const {
   return ntohs(this->port());
 }
 
 inline in_port_t &
-IPEndpoint::port(sockaddr *sa)
-{
-  switch (sa->sa_family) {
+IPEndpoint::port(sockaddr *sa) {
+  switch (sa->sa_family)
+  {
   case AF_INET:
     return reinterpret_cast<sockaddr_in *>(sa)->sin_port;
   case AF_INET6:
@@ -849,14 +972,12 @@ IPEndpoint::port(sockaddr *sa)
 }
 
 inline in_port_t
-IPEndpoint::port(sockaddr const *addr)
-{
+IPEndpoint::port(sockaddr const *addr) {
   return self_type::port(const_cast<sockaddr *>(addr));
 }
 
 inline in_port_t
-IPEndpoint::host_order_port(sockaddr const *addr)
-{
+IPEndpoint::host_order_port(sockaddr const *addr) {
   return ntohs(self_type::port(addr));
 }
 
@@ -864,110 +985,228 @@ IPEndpoint::host_order_port(sockaddr const *addr)
 
 inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {}
 
-inline IP4Addr::operator in_addr_t() const
-{
-  return _addr;
+inline IP4Addr::IP4Addr(std::string_view const& text) {
+  if (! this->load(text)) {
+    _addr = INADDR_ANY;
+  }
 }
 
-inline auto
-IP4Addr::operator=(in_addr_t ip) -> self_type &
-{
-  _addr = ip;
+inline IP4Addr &
+IP4Addr::operator++() {
+  ++_addr;
   return *this;
 }
 
-inline constexpr IP6Addr::IP6Addr(in6_addr addr) : _addr(addr) {}
+inline IP4Addr &
+IP4Addr::operator--() {
+  --_addr;
+  return *this;
+}
+
+inline in_addr_t IP4Addr::network_order() const {
+  return htonl(_addr);
+}
+
+inline IP4Addr::operator in_addr_t() const {
+  return this->network_order();
+}
 
 inline auto
-IP6Addr::operator=(in6_addr ip) -> self_type &
-{
-  _addr = ip;
+IP4Addr::operator=(in_addr_t ip) -> self_type & {
+  _addr = ntohl(ip);
   return *this;
 }
 
-inline IP6Addr::operator in6_addr const &() const
-{
-  return _addr;
+inline bool operator == (IP4Addr const& lhs, IP4Addr const& rhs) {
+  return lhs._addr == rhs._addr;
 }
 
-// +++ IPRange +++
+inline bool operator != (IP4Addr const& lhs, IP4Addr const& rhs) {
+  return lhs._addr != rhs._addr;
+}
 
-inline bool
-IPRange::empty() const
-{
-  return _min.is_valid() && _max.is_valid();
+inline bool operator < (IP4Addr const& lhs, IP4Addr const& rhs) {
+  return lhs._addr < rhs._addr;
 }
-inline IPAddr const &
-IPRange::min() const
-{
-  return _min;
+
+inline bool operator <= (IP4Addr const& lhs, IP4Addr const& rhs) {
+  return lhs._addr <= rhs._addr;
 }
-inline IPAddr const &
-IPRange::max() const
-{
-  return _max;
+
+inline bool operator > (IP4Addr const& lhs, IP4Addr const& rhs) {
+  return rhs < lhs;
 }
-inline IPRange &
-IPRange::clear()
-{
-  _min.invalidate();
-  _max.invalidate();
+
+inline bool operator >= (IP4Addr const& lhs, IP4Addr const& rhs) {
+  return rhs <= lhs;
+}
+
+inline IP4Addr & IP4Addr::operator&=(IPMask const& mask) {
+  auto n = std::min<unsigned>(32, mask.width());
+  in_addr_t bits = htonl(INADDR_BROADCAST << (32 - n));
+  _addr &= bits;
+  return *this;
+}
+
+inline IP4Addr & IP4Addr::operator|=(IPMask const& mask) {
+  auto n = std::min<unsigned>(32, mask.width());
+  in_addr_t bits = htonl(INADDR_BROADCAST << (32 - n));
+  _addr |= bits;
+  return *this;
+}
+
+// ---
+
+inline IP6Addr::IP6Addr(in6_addr const& addr) {
+  *this = addr;
+}
+
+inline IP6Addr::IP6Addr(std::string_view const& text) {
+  if (! this->load(text)) {
+    this->clear();
+  }
+}
+
+inline in6_addr& IP6Addr::copy_to(in6_addr & addr) const {
+  self_type::reorder(addr, _addr._raw);
+  return addr;
+}
+
+inline in6_addr IP6Addr::network_order() const {
+  in6_addr zret;
+  return this->copy_to(zret);
+}
+
+inline auto IP6Addr::operator = (in6_addr const& addr) -> self_type & {
+  self_type::reorder(_addr._raw, addr);
+  return *this;
+}
+
+inline auto IP6Addr::operator = (sockaddr_in6 const* addr) -> self_type & {
+  if (addr) {
+    return *this = addr->sin6_addr;
+  }
+  this->clear();
+}
+
+inline IP6Addr &
+IP6Addr::operator++() {
+  if (++(_addr._u64[1]) == 0) {
+    ++(_addr._u64[0]);
+  }
+  return *this;
+}
+
+inline IP6Addr &
+IP6Addr::operator--() {
+  if (--(_addr._u64[1]) == ~static_cast<uint64_t >(0)) {
+    --(_addr._u64[0]);
+  }
   return *this;
 }
 
+inline void IP6Addr::reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]) {
+  for ( size_t idx = 0 ; idx < WORD_SIZE ; ++idx ) {
+    dst[idx] = src[WORD_SIZE - (idx + 1)];
+  }
+}
+
+inline bool operator == (IP6Addr const& lhs, IP6Addr const& rhs) {
+  return lhs._addr._u64[0] == rhs._addr._u64[0] &&
+         lhs._addr._u64[1] == rhs._addr._u64[1];
+}
+
+inline bool operator != (IP6Addr const& lhs, IP6Addr const& rhs) {
+  return lhs._addr._u64[0] != rhs._addr._u64[0] ||
+         lhs._addr._u64[1] != rhs._addr._u64[1];
+}
+
+inline bool operator < (IP6Addr const& lhs, IP6Addr const& rhs) {
+  return lhs._addr._u64[0] < rhs._addr._u64[0] || (lhs._addr._u64[0] == rhs._addr._u64[0] && lhs._addr._u64[1] < rhs._addr._u64[1]);
+}
+
+inline bool operator > (IP6Addr const& lhs, IP6Addr const& rhs) {
+  return rhs < lhs;
+}
+
+inline bool operator <= (IP6Addr const& lhs, IP6Addr const& rhs) {
+  return lhs._addr._u64[0] < rhs._addr._u64[0] || (lhs._addr._u64[0] == rhs._addr._u64[0] && lhs._addr._u64[1] <= rhs._addr._u64[1]);
+}
+
+inline bool operator >= (IP6Addr const& lhs, IP6Addr const& rhs) {
+  return rhs <= lhs;
+}
+
+// +++ IPRange +++
+
 // +++ IpMask +++
 
-inline IpMask::IpMask() {}
-inline IpMask::IpMask(raw_type width, sa_family_t family) : _mask(width), _family(family) {}
+inline IPMask::IPMask(raw_type width, sa_family_t family) : _mask(width), _family(family) {}
 
-inline IpMask::raw_type
-IpMask::width() const
-{
+inline IPMask::raw_type
+IPMask::width() const {
   return _mask;
 }
 
 inline bool
-operator==(IpMask const &lhs, IpMask const &rhs)
-{
+operator==(IPMask const &lhs, IPMask const &rhs) {
   return lhs.width() == rhs.width();
 }
 
 inline bool
-operator!=(IpMask const &lhs, IpMask const &rhs)
-{
+operator!=(IPMask const &lhs, IPMask const &rhs) {
   return lhs.width() != rhs.width();
 }
 
 inline bool
-operator<(IpMask const &lhs, IpMask const &rhs)
-{
+operator<(IPMask const &lhs, IPMask const &rhs) {
   return lhs.width() < rhs.width();
 }
 
 inline IpNet::IpNet() {}
 
-inline IpNet::IpNet(IPAddr const &addr, IpMask const &mask) : _addr(addr), _mask(mask) {}
+inline IpNet::IpNet(IPAddr const &addr, IPMask const &mask) : _addr(addr), _mask(mask) {}
 
-inline IpNet::operator IPAddr const &() const
-{
+inline IpNet::operator IPAddr const &() const {
   return _addr;
 }
 
-inline IpNet::operator IpMask const &() const
-{
+inline IpNet::operator IPMask const &() const {
   return _mask;
 }
 
 inline IPAddr const &
-IpNet::addr() const
-{
+IpNet::addr() const {
   return _addr;
 }
 
-inline IpMask const &
-IpNet::mask() const
-{
+inline IPMask const &
+IpNet::mask() const {
   return _mask;
 }
 
+// --- IPSpace
+
+template < typename PAYLOAD > auto IPSpace<PAYLOAD>::mark(swoc::IP4Range const &r, PAYLOAD const &payload) -> self_type & {
+  _ip4.mark(r, payload);
+  return *this;
+}
+
+template<typename PAYLOAD>
+void IPSpace<PAYLOAD>::clear() {
+  _ip4.clear();
+  _ip6.clear();
+}
+
+template<typename PAYLOAD>
+template<typename F>
+auto IPSpace<PAYLOAD>::blend(IPRange const&range, PAYLOAD const&payload, F&&blender) -> self_type &{
+  if (range.is(AF_INET)) {
+    _ip4.blend(range, payload, blender);
+  } else if (range.is(AF_INET6)) {
+    _ip6.blend(range, payload, blender);
+  }
+  return *this;
+}
+
 } // namespace swoc
diff --git a/swoc++/include/swoc/swoc_meta.h b/swoc++/include/swoc/swoc_meta.h
index 13324b4..852a7e0 100644
--- a/swoc++/include/swoc/swoc_meta.h
+++ b/swoc++/include/swoc/swoc_meta.h
@@ -137,9 +137,7 @@ namespace meta
    *   }, v);
    * @endcode
    */
-  template <typename... Args> struct vary : public Args... {
-    using Args::operator()...;
-  };
+  template <typename... Args> struct vary : public Args... { using Args::operator()...; };
   /// Template argument deduction guide (C++17 required).
   template <typename... Args> vary(Args...)->vary<Args...>;
 
@@ -201,5 +199,5 @@ namespace meta
     template <typename T> static constexpr bool contains = is_any_of<T, Types...>::value;
   };
 
-}; // namespace meta
+} // namespace meta
 } // namespace swoc
diff --git a/swoc++/include/swoc/swoc_version.h b/swoc++/include/swoc/swoc_version.h
index 36dea4b..1800d03 100644
--- a/swoc++/include/swoc/swoc_version.h
+++ b/swoc++/include/swoc/swoc_version.h
@@ -37,8 +37,6 @@
 
 namespace swoc
 {
-static constexpr unsigned MAJOR_VERSION = 1
-static constexpr unsigned MINOR_VERSION = 0
-static constexpr unsigned POINT_VERSION = 9
+static constexpr unsigned MAJOR_VERSION = 1 static constexpr unsigned MINOR_VERSION = 0 static constexpr unsigned POINT_VERSION = 9
 
 } // namespace swoc
diff --git a/swoc++/src/RBTree.cc b/swoc++/src/RBTree.cc
index a5099e8..180ca51 100644
--- a/swoc++/src/RBTree.cc
+++ b/swoc++/src/RBTree.cc
@@ -300,7 +300,7 @@ namespace detail
             parent    = n->_parent;
             d         = Direction::NONE; // Cancel any leaf node logic
           } else {
-            if (wfc->_color == Color::BLACK) {
+            if (wfc == Color::BLACK) {
               w->child_at(near)->_color = Color::BLACK;
               w->_color                 = Color::RED;
               w->rotate(far);
diff --git a/swoc++/src/bw_ip_format.cc b/swoc++/src/bw_ip_format.cc
index 2fe2a9d..0cf041e 100644
--- a/swoc++/src/bw_ip_format.cc
+++ b/swoc++/src/bw_ip_format.cc
@@ -49,9 +49,9 @@ namespace swoc
 using bwf::Spec;
 
 BufferWriter &
-bwformat(BufferWriter &w, Spec const &spec, in_addr const &addr)
+bwformat(BufferWriter &w, Spec const &spec, in_addr_t addr)
 {
-  auto *ptr = reinterpret_cast<uint8_t const *>(&addr.s_addr);
+  in_addr_t host { ntohl(addr) };
   Spec local_spec{spec}; // Format for address elements.
   bool align_p = false;
 
@@ -72,13 +72,13 @@ bwformat(BufferWriter &w, Spec const &spec, in_addr const &addr)
     local_spec._min = 0;
   }
 
-  bwformat(w, local_spec, ptr[0]);
+  bwformat(w, local_spec, static_cast<uint8_t>(host >> 24 & 0xFF));
   w.write('.');
-  bwformat(w, local_spec, ptr[1]);
+  bwformat(w, local_spec, static_cast<uint8_t>(host >> 16 & 0xFF));
   w.write('.');
-  bwformat(w, local_spec, ptr[2]);
+  bwformat(w, local_spec, static_cast<uint8_t>(host >> 8 & 0xFF));
   w.write('.');
-  bwformat(w, local_spec, ptr[3]);
+  bwformat(w, local_spec, static_cast<uint8_t>(host & 0xFF));
   return w;
 }
 
@@ -182,9 +182,9 @@ bwformat(BufferWriter &w, Spec const &spec, IPAddr const &addr)
 
   if (addr_p) {
     if (addr.is_ip4()) {
-      swoc::bwformat(w, spec, in_addr{addr.raw_ip4()});
+      swoc::bwformat(w, spec, addr.network_ip4());
     } else if (addr.is_ip6()) {
-      swoc::bwformat(w, spec, addr.raw_ip6());
+      swoc::bwformat(w, spec, addr.network_ip6());
     } else {
       w.print("*Not IP address [{}]*", addr.family());
     }
@@ -253,7 +253,7 @@ bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr)
     bool bracket_p = false;
     switch (addr->sa_family) {
     case AF_INET:
-      bwformat(w, spec, reinterpret_cast<sockaddr_in const *>(addr)->sin_addr);
+      bwformat(w, spec, reinterpret_cast<sockaddr_in const *>(addr)->sin_addr.s_addr);
       break;
     case AF_INET6:
       if (port_p) {
diff --git a/swoc++/src/swoc_ip.cc b/swoc++/src/swoc_ip.cc
index 92039e6..076db6b 100644
--- a/swoc++/src/swoc_ip.cc
+++ b/swoc++/src/swoc_ip.cc
@@ -18,6 +18,7 @@
   the License.
  */
 
+#include <swoc/swoc_ip.h>
 #include "swoc/swoc_ip.h"
 #include "swoc/swoc_meta.h"
 
@@ -51,23 +52,24 @@ Set_Sockaddr_Len(T *addr)
 
 } // namespace
 
-namespace swoc
-{
+namespace swoc {
 IPAddr const IPAddr::INVALID;
+IP4Addr const IP4Addr::MIN{INADDR_ANY};
+IP4Addr const IP4Addr::MAX{INADDR_BROADCAST};
+IP6Addr const IP6Addr::MIN{0, 0};
+IP6Addr const IP6Addr::MAX{std::numeric_limits<uint64_t>::max()
+                           , std::numeric_limits<uint64_t>::max()};
 
 bool
-IPEndpoint::assign(sockaddr *dst, sockaddr const *src)
-{
+IPEndpoint::assign(sockaddr *dst, sockaddr const *src) {
   size_t n = 0;
   if (dst != src) {
     self_type::invalidate(dst);
     switch (src->sa_family) {
-    case AF_INET:
-      n = sizeof(sockaddr_in);
-      break;
-    case AF_INET6:
-      n = sizeof(sockaddr_in6);
-      break;
+      case AF_INET:n = sizeof(sockaddr_in);
+        break;
+      case AF_INET6:n = sizeof(sockaddr_in6);
+        break;
     }
     if (n) {
       memcpy(dst, src, n);
@@ -76,39 +78,36 @@ IPEndpoint::assign(sockaddr *dst, sockaddr const *src)
   return n != 0;
 }
 
-IPEndpoint &
-IPEndpoint::assign(IPAddr const &src, in_port_t port)
-{
+IPEndpoint&
+IPEndpoint::assign(IPAddr const&src, in_port_t port) {
   switch (src.family()) {
-  case AF_INET: {
-    memset(&sa4, 0, sizeof sa4);
-    sa4.sin_family      = AF_INET;
-    sa4.sin_addr.s_addr = src.raw_ip4();
-    sa4.sin_port        = port;
-    Set_Sockaddr_Len(&sa4);
-  } break;
-  case AF_INET6: {
-    memset(&sa6, 0, sizeof sa6);
-    sa6.sin6_family = AF_INET6;
-    sa6.sin6_addr   = src.raw_ip6();
-    sa6.sin6_port   = port;
-    Set_Sockaddr_Len(&sa6);
-  } break;
+    case AF_INET: {
+      memset(&sa4, 0, sizeof sa4);
+      sa4.sin_family = AF_INET;
+      sa4.sin_addr.s_addr = src.network_ip4();
+      sa4.sin_port = port;
+      Set_Sockaddr_Len(&sa4);
+    }
+      break;
+    case AF_INET6: {
+      memset(&sa6, 0, sizeof sa6);
+      sa6.sin6_family = AF_INET6;
+      sa6.sin6_addr = src.network_ip6();
+      sa6.sin6_port = port;
+      Set_Sockaddr_Len(&sa6);
+    }
+      break;
   }
   return *this;
 }
 
-IPAddr &
-IPAddr::assign(sockaddr const *addr)
-{
+IPAddr&
+IPAddr::assign(sockaddr const *addr) {
   if (addr) {
     switch (addr->sa_family) {
-    case AF_INET:
-      return this->assign(reinterpret_cast<sockaddr_in const *>(addr)->sin_addr.s_addr);
-    case AF_INET6:
-      return this->assign(reinterpret_cast<sockaddr_in6 const *>(addr)->sin6_addr);
-    default:
-      break;
+      case AF_INET:return this->assign(reinterpret_cast<sockaddr_in const *>(addr)->sin_addr.s_addr);
+      case AF_INET6:return this->assign(reinterpret_cast<sockaddr_in6 const *>(addr)->sin6_addr);
+      default:break;
     }
   }
   _family = AF_UNSPEC;
@@ -116,8 +115,8 @@ IPAddr::assign(sockaddr const *addr)
 }
 
 bool
-IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_view *port, std::string_view *rest)
-{
+IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_view *port
+                     , std::string_view *rest) {
   TextView src(str); /// Easier to work with for parsing.
   // In case the incoming arguments are null, set them here and only check for null once.
   // it doesn't matter if it's all the same, the results will be thrown away.
@@ -152,7 +151,7 @@ IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_v
       TextView::size_type last = src.rfind(':');
       if (last != TextView::npos && last == src.find(':')) {
         // Exactly one colon - leave post colon stuff in @a src.
-        *addr   = src.take_prefix(last);
+        *addr = src.take_prefix(last);
         colon_p = true;
       } else { // presume no port, use everything.
         *addr = src;
@@ -175,15 +174,14 @@ IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_v
 }
 
 bool
-IPEndpoint::parse(std::string_view const &str)
-{
+IPEndpoint::parse(std::string_view const&str) {
   TextView addr_str, port_str, rest;
   TextView src{TextView{str}.trim_if(&isspace)};
 
   if (this->tokenize(src, &addr_str, &port_str, &rest)) {
     if (rest.empty()) {
       IPAddr addr;
-      if (addr.parse(addr_str)) {
+      if (addr.load(addr_str)) {
         auto n{swoc::svto_radix<10>(port_str)};
         if (port_str.empty() && 0 < n && n <= std::numeric_limits<in_port_t>::max()) {
           this->assign(addr, htons(n));
@@ -196,120 +194,99 @@ IPEndpoint::parse(std::string_view const &str)
 }
 
 sockaddr *
-IPAddr::fill(sockaddr *sa, in_port_t port) const
-{
+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)};
-    memset(sa4, 0, sizeof(*sa4));
-    sa4->sin_addr.s_addr = _addr._ip4;
-    sa4->sin_port        = port;
-    Set_Sockaddr_Len(sa4);
-  } break;
-  case AF_INET6: {
-    sockaddr_in6 *sa6{reinterpret_cast<sockaddr_in6 *>(sa)};
-    memset(sa6, 0, sizeof(*sa6));
-    sa6->sin6_port = port;
-    Set_Sockaddr_Len(sa6);
-  } break;
+    case AF_INET: {
+      sockaddr_in *sa4{reinterpret_cast<sockaddr_in *>(sa)};
+      memset(sa4, 0, sizeof(*sa4));
+      sa4->sin_addr.s_addr = _addr._ip4;
+      sa4->sin_port = port;
+      Set_Sockaddr_Len(sa4);
+    }
+      break;
+    case AF_INET6: {
+      sockaddr_in6 *sa6{reinterpret_cast<sockaddr_in6 *>(sa)};
+      memset(sa6, 0, sizeof(*sa6));
+      sa6->sin6_port = port;
+      Set_Sockaddr_Len(sa6);
+    }
+      break;
   }
   return sa;
 }
 
 socklen_t
-IPEndpoint::size() const
-{
+IPEndpoint::size() const {
   switch (sa.sa_family) {
-  case AF_INET:
-    return sizeof(sockaddr_in);
-  case AF_INET6:
-    return sizeof(sockaddr_in6);
-  default:
-    return sizeof(sockaddr);
+    case AF_INET:return sizeof(sockaddr_in);
+    case AF_INET6:return sizeof(sockaddr_in6);
+    default:return sizeof(sockaddr);
   }
 }
 
 std::string_view
-IPEndpoint::family_name(uint16_t family)
-{
+IPEndpoint::family_name(uint16_t family) {
   switch (family) {
-  case AF_INET:
-    return "ipv4"_sv;
-  case AF_INET6:
-    return "ipv6"_sv;
-  case AF_UNIX:
-    return "unix"_sv;
-  case AF_UNSPEC:
-    return "unspec"_sv;
+    case AF_INET:return "ipv4"_sv;
+    case AF_INET6:return "ipv6"_sv;
+    case AF_UNIX:return "unix"_sv;
+    case AF_UNSPEC:return "unspec"_sv;
   }
   return "unknown"_sv;
 }
 
-IPEndpoint &
-IPEndpoint::set_to_any(int family)
-{
+IPEndpoint&
+IPEndpoint::set_to_any(int family) {
   memset(*this, 0, sizeof(*this));
   if (AF_INET == family) {
-    sa4.sin_family      = family;
+    sa4.sin_family = family;
     sa4.sin_addr.s_addr = INADDR_ANY;
     Set_Sockaddr_Len(&sa4);
   } else if (AF_INET6 == family) {
     sa6.sin6_family = family;
-    sa6.sin6_addr   = in6addr_any;
+    sa6.sin6_addr = in6addr_any;
     Set_Sockaddr_Len(&sa6);
   }
   return *this;
 }
 
-IPEndpoint &
-IPEndpoint::set_to_loopback(int family)
-{
+IPEndpoint&
+IPEndpoint::set_to_loopback(int family) {
   memset(*this, 0, sizeof(*this));
   if (AF_INET == family) {
-    sa.sa_family        = family;
+    sa.sa_family = family;
     sa4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
     Set_Sockaddr_Len(&sa4);
   } else if (AF_INET6 == family) {
     static const in6_addr init = IN6ADDR_LOOPBACK_INIT;
-    sa.sa_family               = family;
-    sa6.sin6_addr              = init;
+    sa.sa_family = family;
+    sa6.sin6_addr = init;
     Set_Sockaddr_Len(&sa6);
   }
   return *this;
 }
 
 bool
-IP4Addr::parse(std::string_view const &text)
-{
+IP4Addr::load(std::string_view const&text) {
   TextView src{text};
-  unsigned int n = 0; /// # of octets
-  bool bracket_p = false;
+  int n = SIZE; /// # of octets
 
-  if (src && '[' == *src) {
-    ++src;
-    src.ltrim_if(&isspace);
-    bracket_p = true;
+  if (src.empty() || ('[' == *src && ((++src).empty() || src.back() != ']'))) {
+    return false;
   }
 
-  uint8_t *octet = reinterpret_cast<uint8_t *>(&_addr);
-  while (n < SIZE && !src.empty()) {
+  auto octet = reinterpret_cast<uint8_t *>(&_addr);
+  while (n > 0 && !src.empty()) {
     TextView token{src.take_prefix_at('.')};
     auto x = svto_radix<10>(token);
     if (token.empty() && 0 <= x && x <= std::numeric_limits<uint8_t>::max()) {
-      octet[n++] = x;
+      octet[--n] = x;
     } else {
       break;
     }
   }
 
-  if (bracket_p) {
-    src.ltrim_if(&isspace);
-    if (']' != *src) {
-      n = 0; // pretend failure.
-    }
-  }
-
-  if (n == SIZE && src.empty()) {
+  if (n == 0 && src.empty()) {
     return true;
   }
   _addr = INADDR_ANY;
@@ -317,33 +294,58 @@ IP4Addr::parse(std::string_view const &text)
 }
 
 bool
-IP6Addr::parse(std::string_view const &str)
-{
+IP6Addr::load(std::string_view const&str) {
   TextView src{str};
-  int n          = 0;
-  int empty_idx  = -1; // index of empty quad, -1 if not found yet.
-  bool bracket_p = false;
-  QUAD *quad     = reinterpret_cast<QUAD *>(&_addr);
+  int n = 0;
+  int empty_idx = -1;
+  auto quad = _addr._quad.data();
 
   if (src && '[' == *src) {
     ++src;
-    src.ltrim_if(&isspace);
-    bracket_p = true;
+    if (src.empty() || src.back() != ']') {
+      return false;
+    }
+    src.remove_suffix(1);
+  }
+
+  if (src.size() < 2) {
+    return false;
   }
 
+  // If the first character is ':' then it is an error for it not to be followed by another ':'.
+  // Special case the empty address and loopback, otherwise just make a note of the leading '::'.
+  if (src[0] == ':') {
+    if (src[1] == ':') {
+      if (src.size() == 2) {
+        this->clear();
+        return true;
+      } else if (src.size() == 3 && src[2] == '1') {
+        _addr._u64[0] = 0;
+        _addr._u64[1] = 1;
+        return true;
+      } else {
+        empty_idx = n;
+        src.remove_prefix(2);
+      }
+    } else {
+      return false;
+    }
+  }
+
+  // Sadly the empty quads can't be done in line because it's not possible to know the correct index
+  // of the next present quad until the entire address has been parsed.
   while (n < N_QUADS && !src.empty()) {
     TextView token{src.take_prefix_at(':')};
     if (token.empty()) {
-      if (empty_idx > 0 || (empty_idx == 0 && n > 1)) {
-        // two empty slots OK iff it's the first two (e.g. "::1"), otherwise invalid.
-        break;
+      if (empty_idx >= 0) { // two instances of "::", fail.
+        return false;
       }
       empty_idx = n;
     } else {
       TextView r;
       auto x = svtoi(token, &r, 16);
       if (r.size() == token.size()) {
-        quad[n++] = htons(x);
+        quad[QUAD_IDX[n++]] = x;
       } else {
         break;
       }
@@ -351,41 +353,42 @@ IP6Addr::parse(std::string_view const &str)
   }
 
   // Handle empty quads - invalid if empty and still had a full set of quads
-  if (empty_idx >= 0 && n < N_QUADS) {
-    if (static_cast<int>(n) <= empty_idx) {
-      while (empty_idx < static_cast<int>(N_QUADS)) {
-        quad[empty_idx++] = 0;
+  if (empty_idx >= 0) {
+    if (n < N_QUADS) {
+      auto nil_idx = N_QUADS - (n - empty_idx);
+      auto delta = N_QUADS - n;
+      for (int k = N_QUADS - 1; k >= empty_idx; --k) {
+        quad[QUAD_IDX[k]] = (k >= nil_idx ? quad[QUAD_IDX[k - delta]] : 0);
       }
+      n = N_QUADS; // Mark success.
     } else {
-      int k = 1;
-      for (; n - k >= empty_idx; ++k) {
-        quad[N_QUADS - k] = quad[n - k];
-      }
-      for (; N_QUADS - k >= empty_idx; ++k) {
-        quad[N_QUADS - k] = 0;
-        ++n; // track this so the validity check does the right thing.
-      }
-    }
-  }
-
-  if (bracket_p) {
-    src.ltrim_if(&isspace);
-    if (']' != *src) {
-      n = 0; // pretend failure.
+      return false; // too many quads - full set plus empty.
     }
   }
 
   if (n == N_QUADS && src.empty()) {
     return true;
   }
-  memset(&_addr, 0, sizeof(_addr));
+
+  this->clear();
   return false;
 }
 
+// These are Intel correct, at some point will need to be architecture dependent.
+
+void IP6Addr::reorder(in6_addr&dst, raw_type const&src) {
+  self_type::reorder(dst.s6_addr, src.data());
+  self_type::reorder(dst.s6_addr + WORD_SIZE, src.data() + WORD_SIZE);
+}
+
+void IP6Addr::reorder(raw_type&dst, in6_addr const&src) {
+  self_type::reorder(dst.data(), src.s6_addr);
+  self_type::reorder(dst.data() + WORD_SIZE, src.s6_addr + WORD_SIZE);
+}
+
 bool
-IPAddr::parse(const std::string_view &str)
-{
-  TextView src{str};
+IPAddr::load(const std::string_view&text) {
+  TextView src{text};
   src.ltrim_if(&isspace);
 
   if (TextView::npos != src.prefix(5).find_first_of('.')) {
@@ -398,16 +401,16 @@ IPAddr::parse(const std::string_view &str)
 
   // Do the real parse now
   switch (_family) {
-  case AF_INET:
-    if (!_addr._ip4.parse(src)) {
-      _family = AF_UNSPEC;
-    }
-    break;
-  case AF_INET6:
-    if (!_addr._ip6.parse(src)) {
-      _family = AF_UNSPEC;
-    }
-    break;
+    case AF_INET:
+      if (!_addr._ip4.load(src)) {
+        _family = AF_UNSPEC;
+      }
+      break;
+    case AF_INET6:
+      if (!_addr._ip6.load(src)) {
+        _family = AF_UNSPEC;
+      }
+      break;
   }
   return this->is_valid();
 }
@@ -488,100 +491,163 @@ IPAddr::cmp(self_type const &that) const
 #endif
 
 bool
-IPAddr::is_multicast() const
-{
-  return (AF_INET == _family && 0xe == (_addr._octet[0] >> 4)) || (AF_INET6 == _family && IN6_IS_ADDR_MULTICAST(&_addr._ip6));
+IPAddr::is_multicast() const {
+  return (AF_INET == _family && 0xe == (_addr._octet[0] >> 4)) ||
+         (AF_INET6 == _family && IN6_IS_ADDR_MULTICAST(&_addr._ip6));
 }
 
-IPEndpoint::IPEndpoint(std::string_view const &text)
-{
+IPEndpoint::IPEndpoint(std::string_view const&text) {
   this->invalidate();
   this->parse(text);
 }
 
-#if 0
-bool
-IPRange::parse(std::string_view src)
-{
-  bool zret = false;
-  static const IPAddr ZERO_ADDR4{INADDR_ANY};
-  static const IPAddr MAX_ADDR4{INADDR_BROADCAST};
-  static const IPAddr ZERO_ADDR6{in6addr_any};
-  // I can't find a clean way to static const initialize an IPv6 address to all ones.
-  // This is the best I can find that's portable.
-  static const uint64_t ones[]{UINT64_MAX, UINT64_MAX};
-  static const IPAddr MAX_ADDR6{reinterpret_cast<in6_addr const &>(ones)};
-
-  auto idx = src.find_first_of("/-"_sv);
-  if (idx != src.npos) {
-    if (idx + 1 >= src.size()) { // must have something past the separator or it's bogus.
-      ;                          // empty
-    } else if ('/' == src[idx]) {
-      if (_min.parse(src.substr(0, idx))) { // load the address
-        TextView parsed;
-        src.remove_prefix(idx + 1); // drop address and separator.
-        int cidr = svtou(src, &parsed);
-        if (parsed.size() && 0 <= cidr) { // a cidr that's a positive integer.
-          // Special case the cidr sizes for 0, maximum, and for IPv6 64 bit boundaries.
-          if (_min.is_ip4()) {
-            zret = true;
-            if (0 == cidr) {
-              _min = ZERO_ADDR4;
-              _max = MAX_ADDR4;
-            } else if (cidr <= 32) {
-              _max = _min;
-              if (cidr < 32) {
-                in_addr_t mask = htonl(INADDR_BROADCAST << (32 - cidr));
-                _min._addr._ip4 &= mask;
-                _max._addr._ip4 |= ~mask;
-              }
-            } else {
-              zret = false;
-            }
-          } else if (_min.is_ip6()) {
-            uint64_t mask;
-            zret = true;
-            if (cidr == 0) {
-              _min = ZERO_ADDR6;
-              _max = MAX_ADDR6;
-            } else if (cidr < 64) { // only _max bytes affected, lower bytes are forced.
-              mask         = htobe64(~static_cast<uint64_t>(0) << (64 - cidr));
-              _max._family = _min._family;
-              _min._addr._u64[0] &= mask;
-              _min._addr._u64[1] = 0;
-              _max._addr._u64[0] = _min._addr._u64[0] | ~mask;
-              _max._addr._u64[1] = ~static_cast<uint64_t>(0);
-            } else if (cidr == 64) {
-              _max._family       = _min._family;
-              _max._addr._u64[0] = _min._addr._u64[0];
-              _min._addr._u64[1] = 0;
-              _max._addr._u64[1] = ~static_cast<uint64_t>(0);
-            } else if (cidr <= 128) { // lower bytes changed, _max bytes unaffected.
-              _max = _min;
-              if (cidr < 128) {
-                mask = htobe64(~static_cast<uint64_t>(0) << (128 - cidr));
-                _min._addr._u64[1] &= mask;
-                _max._addr._u64[1] |= ~mask;
-              }
-            } else {
-              zret = false;
-            }
+bool IPMask::load(string_view const&text) {
+  TextView parsed;
+  _mask = swoc::svtou(text, &parsed);
+  if (parsed.size() != text.size()) {
+    _mask = 0;
+    return false;
+  }
+  return true;
+}
+
+IP4Range::IP4Range(swoc::IP4Addr const&addr, swoc::IPMask const&mask) {
+  this->assign(addr, mask);
+}
+
+IP4Range&IP4Range::assign(swoc::IP4Addr const&addr, swoc::IPMask const&mask) {
+  // Special case the cidr sizes for 0, maximum
+  if (0 == mask.width()) {
+    _min = metric_type::MIN;
+    _max = metric_type::MAX;
+  } else {
+    _min = _max = addr;
+    if (mask.width() < 32) {
+      in_addr_t bits = INADDR_BROADCAST << (32 - mask.width());
+      _min._addr &= bits;
+      _max._addr |= ~bits;
+    }
+  }
+  return *this;
+}
+
+bool IP4Range::load(string_view text) {
+  static const string_view SEPARATORS("/-");
+  auto idx = text.find_first_of(SEPARATORS);
+  if (idx != text.npos) {
+    if (idx + 1 < text.size()) { // must have something past the separator or it's bogus.
+      if ('/' == text[idx]) {
+        metric_type addr;
+        if (addr.load(text.substr(0, idx))) { // load the address
+          IPMask mask;
+          text.remove_prefix(idx + 1); // drop address and separator.
+          if (mask.load(text)) {
+            this->assign(addr, mask);
+            return true;
           }
         }
+      } else if (_min.load(text.substr(0, idx)) && _max.load(text.substr(idx + 1))) {
+        return true;
       }
-    } else if (_min.parse(src.substr(0, idx)) && _max.parse(src.substr(idx + 1)) && _min.family() == _max.family()) {
-      // not '/' so must be '-'
-      zret = true;
     }
-  } else if (_min.parse(src)) {
-    zret = true;
+  } else if (_min.load(text)) {
     _max = _min;
+    return true;
   }
 
-  if (!zret)
-    this->clear();
-  return zret;
+  this->clear();
+  return false;
+}
+
+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;
+  } 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;
+  } else if (cidr <= 128) { // _min bytes changed, _max bytes unaffected.
+    _min = _max = addr;
+    if (cidr < 128) {
+      auto bits = FULL_MASK << (128 - cidr);
+      _min._addr._u64[1] &= bits;
+      _max._addr._u64[1] |= ~bits;
+    }
+  }
+  return *this;
+}
+
+bool IP6Range::load(std::string_view text) {
+  static const string_view SEPARATORS("/-");
+  auto idx = text.find_first_of(SEPARATORS);
+  if (idx != text.npos) {
+    if (idx + 1 < text.size()) { // must have something past the separator or it's bogus.
+      if ('/' == text[idx]) {
+        metric_type addr;
+        if (addr.load(text.substr(0, idx))) { // load the address
+          IPMask mask;
+          text.remove_prefix(idx + 1); // drop address and separator.
+          if (mask.load(text)) {
+            this->assign(addr, mask);
+            return true;
+          }
+        }
+      } else if (_min.load(text.substr(0, idx)) && _max.load(text.substr(idx + 1))) {
+        return true;
+      }
+    }
+  } else if (_min.load(text)) {
+    _max = _min;
+    return true;
+  }
+  this->clear();
+  return false;
+}
+
+bool IPRange::load(std::string_view const& text) {
+  static const string_view CHARS{".:"};
+  auto idx = text.find_first_of(CHARS);
+  if (idx != text.npos) {
+    if (text[idx] == '.') {
+      if (_range._ip4.load(text)) {
+        _family = AF_INET;
+        return true;
+      }
+    } else {
+      if (_range._ip6.load(text)) {
+        _family = AF_INET6;
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+IPAddr IPRange::min() const {
+  switch(_family) {
+    case AF_INET: return _range._ip4.min();
+    case AF_INET6: return _range._ip6.min();
+    default: break;
+  }
+  return {};
+}
+
+IPAddr IPRange::max() const {
+  switch(_family) {
+    case AF_INET: return _range._ip4.max();
+    case AF_INET6: return _range._ip6.max();
+    default: break;
+  }
+  return {};
 }
-#endif
 
 } // namespace swoc
diff --git a/unit_tests/ex_TextView.cc b/unit_tests/ex_TextView.cc
index 3157fbb..0894a1e 100644
--- a/unit_tests/ex_TextView.cc
+++ b/unit_tests/ex_TextView.cc
@@ -218,6 +218,9 @@ TEST_CASE("TextView Lines", "[libswoc][example][textview][lines]")
   REQUIRE(n_lines == 86);
 };
 
+#include <set>
+#include <swoc/swoc_ip.h>
+
 TEST_CASE("TextView misc", "[libswoc][example][textview][misc]")
 {
   TextView src = "  alpha.bravo.old:charlie.delta.old  :  echo.foxtrot.old  ";
@@ -226,3 +229,65 @@ TEST_CASE("TextView misc", "[libswoc][example][textview][misc]")
   REQUIRE("echo.foxtrot" == src.take_prefix_at(':').remove_suffix_at('.').ltrim_if(&isspace));
   REQUIRE(src.empty());
 }
+
+TEST_CASE("TextView parsing", "[libswoc][example][text][parsing]") {
+  static const std::set<std::string_view> DC_TAGS {
+      "amb", "ata", "aue", "bga", "bra", "cha", "coa", "daa", "dca", "deb", "dnb", "esa", "fra", "frb"
+    , "hkb", "inc", "ir2", "jpa", "laa", "lob", "mib"
+    , "nya", "rob", "seb", "sgb", "sja", "swb", "tpb", "twb", "via", "waa"
+  };
+  TextView parsed;
+  swoc::IP4Addr addr;
+
+  std::error_code ec;
+  auto data { swoc::file::load("../unit_tests/examples/resolver.txt"_tv, ec) };
+  REQUIRE(data.size() > 2); // if this fails, there's something wrong with the path or current directory.
+
+  TextView content { data };
+  while (content) {
+    auto line { content.take_prefix_at('\n').trim_if(&isspace) }; // get the next line.
+    if (line.empty() || *line == '#') { // skip empty and lines starting with '#'
+      continue;
+    }
+    auto addr_txt = line.take_prefix_at(';');
+    auto conf_txt = line.ltrim_if(&isspace).take_prefix_if(&isspace);
+    auto dcnum_txt = line.ltrim_if(&isspace).take_prefix_if(&isspace);
+    auto dc_txt = line.ltrim_if(&isspace).take_prefix_if(&isspace);
+
+    // First element must be a valid IPv4 address.
+    REQUIRE(addr.load(addr_txt) == true);
+
+    // Confidence value must be an unsigned integer after the '='.
+    auto conf_value {conf_txt.split_suffix_at('=')};
+    swoc::svtou(conf_value, &parsed);
+    REQUIRE(conf_value == parsed); // valid integer
+
+    // Number of elements in @a dc_txt - verify it's an integer.
+    auto dcnum_value {dcnum_txt.split_suffix_at('=')};
+    auto dc_n = swoc::svtou(dcnum_value, &parsed);
+    REQUIRE(dcnum_value == parsed); // valid integer
+
+    // Verify the expected prefix for the DC list.
+    static constexpr TextView DC_PREFIX { "dc=[" };
+    if (!dc_txt.starts_with(DC_PREFIX) ||
+      dc_txt.remove_prefix(DC_PREFIX.size()).empty() ||
+      dc_txt.back() != ']'
+    ) {
+      continue;
+    }
+
+    dc_txt.rtrim("], \t"); // drop trailing brackets, commas, spaces, tabs.
+    // walk the comma separated tokens
+    unsigned dc_count = 0;
+    while (dc_txt) {
+      auto key   = dc_txt.take_prefix_at(',');
+      auto value = key.take_suffix_at('=');
+      auto n = swoc::svtou(value, &parsed);
+      // Each element must be one of the known tags, followed by '=' and an integer.
+      REQUIRE(parsed == value); // value integer.
+      REQUIRE(DC_TAGS.find(key) != DC_TAGS.end());
+      ++dc_count;
+    }
+    REQUIRE(dc_count == dc_n);
+  };
+};
diff --git a/unit_tests/examples/resolver.txt b/unit_tests/examples/resolver.txt
new file mode 100644
index 0000000..f096c93
--- /dev/null
+++ b/unit_tests/examples/resolver.txt
@@ -0,0 +1,17 @@
+172.16.10.10;	conf=45	dcnum=31	dc=[cha=12,dca=30,nya=35,ata=39,daa=41,dnb=56,mib=61,sja=68,laa=69,swb=72,lob=103,fra=109,coa=112,amb=115,ir2=117,deb=122,frb=123,via=128,esa=133,waa=141,seb=141,rob=147,bga=147,bra=169,tpb=217,jpa=218,twb=220,hkb=222,aue=237,inc=240,sgb=245,]
+172.16.10.11;	conf=45	dcnum=31	dc=[cha=17,dca=33,daa=38,nya=40,ata=41,mib=53,dnb=53,swb=63,sja=64,laa=69,lob=106,fra=110,coa=110,amb=111,frb=121,deb=122,esa=123,ir2=128,via=132,seb=139,waa=143,rob=144,bga=145,bra=159,tpb=215,hkb=215,twb=219,jpa=219,inc=226,aue=238,sgb=246,]
+172.16.10.12;	conf=45	dcnum=31	dc=[cha=19,dca=33,nya=40,daa=41,ata=44,mib=52,dnb=53,sja=65,swb=68,laa=71,fra=104,lob=105,coa=110,amb=114,ir2=118,deb=119,frb=122,esa=127,via=128,seb=135,waa=137,rob=143,bga=145,bra=165,tpb=216,jpa=219,hkb=219,twb=222,inc=228,aue=229,sgb=246,]
+172.16.10.13;	conf=45	dcnum=31	dc=[cha=16,dca=30,nya=36,daa=41,ata=47,mib=51,dnb=56,swb=66,sja=66,laa=71,lob=103,coa=107,amb=109,fra=112,ir2=117,deb=118,frb=123,esa=132,via=133,waa=136,bga=141,rob=142,seb=144,bra=167,twb=205,tpb=215,jpa=223,hkb=223,aue=230,inc=233,sgb=242,]
+172.16.10.14;	conf=45	dcnum=31	dc=[cha=19,dca=31,nya=37,ata=44,daa=46,dnb=47,mib=58,swb=65,sja=66,laa=70,lob=104,fra=109,amb=109,coa=112,frb=120,deb=121,ir2=122,esa=125,via=130,waa=141,rob=143,seb=145,bga=155,bra=170,tpb=219,twb=221,jpa=224,inc=227,hkb=227,aue=236,sgb=242,]
+172.16.10.15;	conf=45	dcnum=31	dc=[cha=24,dca=32,nya=37,daa=38,ata=44,dnb=57,mib=64,sja=65,laa=66,swb=68,lob=100,coa=106,fra=112,amb=112,deb=116,ir2=123,esa=124,frb=125,via=128,waa=136,bga=145,rob=148,seb=151,bra=173,twb=206,jpa=217,tpb=227,aue=228,hkb=230,inc=234,sgb=247,]
+172.16.11.10;	conf=45	dcnum=31	dc=[cha=23,dca=33,dnb=35,nya=39,ata=39,daa=44,mib=55,sja=63,swb=69,laa=69,lob=107,fra=110,amb=115,frb=116,ir2=121,coa=121,deb=124,esa=125,via=129,waa=141,seb=141,rob=141,bga=141,bra=163,jpa=213,twb=216,hkb=220,tpb=221,inc=221,aue=239,sgb=246,]
+172.16.11.11;	conf=45	dcnum=31	dc=[cha=15,dca=31,nya=36,ata=37,daa=40,dnb=50,swb=61,mib=62,sja=66,laa=69,coa=107,fra=109,amb=113,deb=117,lob=119,ir2=122,frb=124,esa=125,via=129,waa=137,seb=141,rob=142,bga=148,bra=162,tpb=211,twb=217,jpa=219,hkb=226,inc=231,sgb=243,aue=245,]
+172.16.11.12;	conf=45	dcnum=31	dc=[cha=15,dca=35,nya=36,daa=36,dnb=43,ata=47,mib=50,sja=64,laa=67,swb=69,lob=100,coa=104,amb=113,fra=114,deb=119,ir2=123,frb=123,via=126,esa=129,waa=140,seb=143,bga=148,bra=158,rob=198,jpa=206,twb=209,tpb=217,hkb=217,inc=227,aue=233,sgb=245,]
+172.16.11.13;	conf=45	dcnum=31	dc=[cha=16,dca=33,nya=34,dnb=38,daa=43,ata=44,mib=57,swb=67,sja=70,laa=70,lob=103,coa=106,amb=107,fra=113,ir2=114,frb=119,deb=120,via=128,esa=130,waa=138,seb=139,bga=143,rob=145,bra=170,jpa=213,twb=219,tpb=219,hkb=224,inc=235,aue=239,sgb=248,]
+172.16.11.14;	conf=45	dcnum=31	dc=[cha=18,dca=31,nya=38,daa=41,ata=42,dnb=47,mib=56,sja=65,swb=68,laa=75,lob=103,fra=109,coa=111,amb=114,frb=118,ir2=119,deb=126,via=128,esa=132,waa=136,seb=137,rob=146,bga=146,bra=161,tpb=212,jpa=216,twb=222,inc=223,hkb=224,sgb=242,aue=242,]
+172.16.11.15;	conf=45	dcnum=31	dc=[cha=23,dca=32,nya=36,ata=37,daa=38,dnb=54,sja=66,swb=67,laa=67,mib=73,amb=107,lob=109,fra=109,deb=115,frb=120,coa=125,ir2=126,esa=134,via=137,seb=137,waa=141,rob=142,bga=156,bra=162,tpb=213,twb=222,jpa=224,hkb=228,aue=230,inc=233,sgb=255,]
+172.16.14.10;	conf=45	dcnum=31	dc=[daa=30,ata=38,cha=43,dnb=51,dca=51,mib=54,laa=57,sja=58,nya=60,swb=69,coa=106,lob=127,fra=129,amb=133,ir2=134,deb=143,frb=146,esa=150,via=153,seb=163,rob=165,bga=165,bra=168,waa=169,tpb=204,jpa=207,aue=208,twb=213,hkb=223,sgb=239,inc=271,]
+172.16.14.11;	conf=45	dcnum=31	dc=[daa=24,ata=40,cha=45,dnb=47,laa=55,mib=56,dca=56,nya=57,sja=67,swb=73,coa=111,lob=125,amb=133,ir2=138,fra=140,frb=145,deb=147,via=153,esa=155,waa=157,seb=158,bga=166,bra=171,rob=172,tpb=209,twb=213,jpa=218,hkb=218,aue=223,sgb=243,inc=270,]
+172.16.14.12;	conf=45	dcnum=31	dc=[daa=33,cha=44,dnb=46,ata=48,mib=54,dca=55,nya=56,laa=56,sja=64,swb=72,coa=119,lob=127,amb=132,fra=133,ir2=137,deb=139,frb=140,esa=150,via=154,waa=159,seb=164,bga=168,rob=170,bra=170,jpa=209,twb=212,tpb=212,aue=212,hkb=220,sgb=243,inc=269,]
+172.16.14.13;	conf=45	dcnum=31	dc=[daa=31,cha=43,ata=43,dca=50,mib=52,laa=54,nya=60,sja=61,dnb=61,swb=85,coa=113,lob=127,amb=134,fra=135,ir2=138,deb=144,esa=145,frb=150,waa=156,via=156,seb=166,bga=168,rob=172,bra=174,twb=208,aue=209,hkb=214,jpa=215,tpb=218,sgb=242,inc=271,]
+
diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc
index 97dd3a8..b7a1aee 100644
--- a/unit_tests/test_ip.cc
+++ b/unit_tests/test_ip.cc
@@ -1,33 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014 Network Geographics
+
 /** @file
 
     IP address support testing.
-
-    @section license License
-
-    Licensed to the Apache Software Foundation (ASF) under one or more contributor license
-    agreements.  See the NOTICE file distributed with this work for additional information regarding
-    copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with the License.  You may
-    obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software distributed under the
-    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
-    express or implied. See the License for the specific language governing permissions and
-    limitations under the License.
 */
 
-#include "swoc/swoc_ip.h"
-#include "swoc/bwf_ip.h"
 #include "catch.hpp"
 
+#include <set>
+
+#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;
 
-TEST_CASE("ink_inet", "[libswoc][ip]")
-{
+TEST_CASE("ink_inet", "[libswoc][ip]") {
   // Use TextView because string_view(nullptr) fails. Gah.
   struct ip_parse_spec {
     TextView hostspec;
@@ -37,21 +34,21 @@ TEST_CASE("ink_inet", "[libswoc][ip]")
   };
 
   constexpr ip_parse_spec names[] = {
-    {{"::"}, {"::"}, {nullptr}, {nullptr}},
-    {{"[::1]:99"}, {"::1"}, {"99"}, {nullptr}},
-    {{"127.0.0.1:8080"}, {"127.0.0.1"}, {"8080"}, {nullptr}},
-    {{"127.0.0.1:8080-Bob"}, {"127.0.0.1"}, {"8080"}, {"-Bob"}},
-    {{"127.0.0.1:"}, {"127.0.0.1"}, {nullptr}, {":"}},
-    {{"foo.example.com"}, {"foo.example.com"}, {nullptr}, {nullptr}},
-    {{"foo.example.com:99"}, {"foo.example.com"}, {"99"}, {nullptr}},
-    {{"ffee::24c3:3349:3cee:0143"}, {"ffee::24c3:3349:3cee:0143"}, {nullptr}, {nullptr}},
-    {{"fe80:88b5:4a:20c:29ff:feae:1c33:8080"}, {"fe80:88b5:4a:20c:29ff:feae:1c33:8080"}, {nullptr}, {nullptr}},
-    {{"[ffee::24c3:3349:3cee:0143]"}, {"ffee::24c3:3349:3cee:0143"}, {nullptr}, {nullptr}},
-    {{"[ffee::24c3:3349:3cee:0143]:80"}, {"ffee::24c3:3349:3cee:0143"}, {"80"}, {nullptr}},
-    {{"[ffee::24c3:3349:3cee:0143]:8080x"}, {"ffee::24c3:3349:3cee:0143"}, {"8080"}, {"x"}},
-  };
-
-  for (auto const &s : names) {
+      {  {"::"}                                  , {"::"}                                  , {nullptr}, {nullptr}}
+      , {{"[::1]:99"}                            , {"::1"}                                 , {"99"}   , {nullptr}}
+      , {{"127.0.0.1:8080"}                      , {"127.0.0.1"}                           , {"8080"} , {nullptr}}
+      , {{"127.0.0.1:8080-Bob"}                  , {"127.0.0.1"}                           , {"8080"} , {"-Bob"}}
+      , {{"127.0.0.1:"}                          , {"127.0.0.1"}                           , {nullptr}, {":"}}
+      , {{"foo.example.com"}                     , {"foo.example.com"}                     , {nullptr}, {nullptr}}
+      , {{"foo.example.com:99"}                  , {"foo.example.com"}                     , {"99"}   , {nullptr}}
+      , {{"ffee::24c3:3349:3cee:0143"}           , {"ffee::24c3:3349:3cee:0143"}           , {nullptr}, {nullptr}}
+      , {{"fe80:88b5:4a:20c:29ff:feae:1c33:8080"}, {"fe80:88b5:4a:20c:29ff:feae:1c33:8080"}, {nullptr}, {nullptr}}
+      , {{"[ffee::24c3:3349:3cee:0143]"}         , {"ffee::24c3:3349:3cee:0143"}           , {nullptr}, {nullptr}}
+      , {{"[ffee::24c3:3349:3cee:0143]:80"}      , {"ffee::24c3:3349:3cee:0143"}           , {"80"}   , {nullptr}}
+      , {{"[ffee::24c3:3349:3cee:0143]:8080x"}   , {"ffee::24c3:3349:3cee:0143"}           , {"8080"} , {"x"}}
+      ,};
+
+  for (auto const&s : names) {
     std::string_view host, port, rest;
 
     REQUIRE(IPEndpoint::tokenize(s.hostspec, &host, &port, &rest) == true);
@@ -59,93 +56,69 @@ TEST_CASE("ink_inet", "[libswoc][ip]")
     REQUIRE(s.port == port);
     REQUIRE(s.rest == rest);
   }
-}
 
-#if 0
-TEST_CASE("ats_ip_pton", "[libswoc][inet][ink_inet]")
-{
-  IpEndpoint ep;
-  IpAddr addr;
-  IpAddr lower, upper;
-
-  REQUIRE(0 == ats_ip_pton("76.14.64.156", &ep.sa));
-  REQUIRE(0 == addr.load("76.14.64.156"));
-  REQUIRE(addr.family() == ep.family());
-
-  switch (addr.family()) {
-    case AF_INET:
-      REQUIRE(ep.sin.sin_addr.s_addr == addr._addr._ip4);
-      break;
-    case AF_INET6:
-      REQUIRE(0 == memcmp(&ep.sin6.sin6_addr, &addr._addr._ip6, sizeof(in6_addr)));
-      break;
-    default:;
-  }
+  // Do a bit of IPv6 testing.
+  IP6Addr a6_null;
+  IP6Addr a6_1{"fe80:88b5:4a:20c:29ff:feae:5587:1c33"};
+  IP6Addr a6_2{"fe80:88b5:4a:20c:29ff:feae:5587:1c34"};
+  IP6Addr a6_3{"de80:88b5:4a:20c:29ff:feae:5587:1c35"};
+  IP6Addr a6_4{"fe80:88b5:4a:20c:29ff:feae:5587:1c34"};
+
+  REQUIRE(a6_1 != a6_null);
+  REQUIRE(a6_1 != a6_2);
+  REQUIRE(a6_1 < a6_2);
+  REQUIRE(a6_2 > a6_1);
+  ++a6_1;
+  REQUIRE(a6_1 == a6_2);
+  ++a6_1;
+  REQUIRE(a6_1 != a6_2);
+  REQUIRE(a6_1 > a6_2);
+  REQUIRE(a6_3 != a6_4);
+  REQUIRE(a6_3 < a6_4);
+  REQUIRE(a6_4 > a6_3);
+
+  // For this data, the bytes should be in IPv6 network order.
+  static const std::tuple<TextView, bool, IP6Addr::raw_type> ipv6_ex[] = {
+      {  "::"                                , true , {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
+      , {"::1"                               , true , {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}
+      , {":::"                               , false, {}}
+      , {"fe80::20c:29ff:feae:5587:1c33"     , true , {0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x29, 0xFF, 0xFE, 0xAE, 0x55, 0x87, 0x1C, 0x33}}
+      , {"fe80:20c:29ff:feae:5587::1c33"     , true , {0xFE, 0x80, 0x02, 0x0C, 0x29, 0xFF, 0xFE, 0xAE, 0x55, 0x87, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x33}}
+      , {"fe80:20c:29ff:feae:5587:1c33::"    , true , {0xFE, 0x80, 0x02, 0x0C, 0x29, 0xFF, 0xFE, 0xAE, 0x55, 0x87, 0x1c, 0x33, 0x00, 0x00, 0x00, 0x00}}
+      , {"::fe80:20c:29ff:feae:5587:1c33"    , true , {0x00, 0x00, 0x00, 0x00, 0xFE, 0x80, 0x02, 0x0C, 0x29, 0xFF, 0xFE, 0xAE, 0x55, 0x87, 0x1c, 0x33}}
+      , {":fe80:20c:29ff:feae:5587:4A43:1c33", false, {}}
+      , {"fe80:20c::29ff:feae:5587::1c33"    , false, {}}
+  };
 
-  REQUIRE(TS_SUCCESS != addr.load("Evil Dave Rulz!"));
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("1.1.1.1-2.2.2.2"sv, lower, upper));
-  REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0/", lower, upper));
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("172.16.39.0/24", lower, upper));
-  REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0-", lower, upper));
-  REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0/35", lower, upper));
-  REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0/-20", lower, upper));
-  REQUIRE(TS_SUCCESS != ats_ip_range_parse("Thanks, Persia! You're the best.", lower, upper));
-
-  addr.load("172.16.39.0");
-  REQUIRE(addr == lower);
-  addr.load("172.16.39.255");
-  REQUIRE(addr == upper);
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("10.169.243.105/23", lower, upper));
-  addr.load("10.169.242.0");
-  REQUIRE(lower == addr);
-  addr.load("10.169.243.255");
-  REQUIRE(upper == addr);
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("192.168.99.22", lower, upper));
-  REQUIRE(lower == upper);
-  REQUIRE(lower != IpAddr{INADDR_ANY});
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("0/0", lower, upper));
-  REQUIRE(lower == IpAddr{INADDR_ANY});
-  REQUIRE(upper == IpAddr{INADDR_BROADCAST});
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("c600::-d900::"sv, lower, upper));
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("1300::/96", lower, upper));
-  REQUIRE(TS_SUCCESS != ats_ip_range_parse("ffee::24c3:3349:3cee:0143/", lower, upper));
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("ffee:1337:beef:dead:24c3:3349:3cee:0143/80", lower, upper));
-  addr.load("ffee:1337:beef:dead:24c3::"sv);
-  REQUIRE(lower == addr);
-  addr.load("ffee:1337:beef:dead:24c3:FFFF:FFFF:FFFF"sv);
-  REQUIRE(upper == addr);
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("ffee:1337:beef:dead:24c3:3349:3cee:0143/57", lower, upper));
-  addr.load("ffee:1337:beef:de80::"sv);
-  REQUIRE(lower == addr);
-  addr.load("ffee:1337:beef:deff:FFFF:FFFF:FFFF:FFFF"sv);
-  REQUIRE(upper == addr);
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("ffee::24c3:3349:3cee:0143", lower, upper));
-  REQUIRE(lower == upper);
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("::/0", lower, upper));
-  REQUIRE(lower._addr._u64[0] == 0);
-  REQUIRE(lower._addr._u64[1] == 0);
-  REQUIRE(upper._addr._u64[0] == ~static_cast<uint64_t>(0));
-  REQUIRE(upper._addr._u64[1] == static_cast<uint64_t>(-1));
-
-  REQUIRE(TS_SUCCESS == ats_ip_range_parse("c000::/32", lower, upper));
-  addr.load("c000::");
-  REQUIRE(addr == lower);
-  addr.load("c000::ffff:ffff:ffff:ffff:ffff:ffff");
-  REQUIRE(addr == upper);
-}
-#endif
+  for (auto const&item : ipv6_ex) {
+    auto &&[text, result, data]{item};
+    IP6Addr addr;
+    REQUIRE(result == addr.load(text));
+    if (result) {
+      union {
+        in6_addr _inet;
+        IP6Addr::raw_type _raw;
+      } ar;
+      ar._inet = addr.network_order();
+      REQUIRE(ar._raw == data);
+    }
+  }
 
-TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]")
-{
+  IP4Range r4;
+  REQUIRE(r4.load("10.242.129.0-10.242.129.127") == true);
+  REQUIRE(r4.min() == IP4Addr("10.242.129.0"));
+  REQUIRE(r4.max() == IP4Addr("10.242.129.127"));
+  REQUIRE(r4.load("10.242.129.0/25") == true);
+  REQUIRE(r4.min() == IP4Addr("10.242.129.0"));
+  REQUIRE(r4.max() == IP4Addr("10.242.129.127"));
+  REQUIRE(r4.load("2.2.2.2") == true);
+  REQUIRE(r4.min() == IP4Addr("2.2.2.2"));
+  REQUIRE(r4.max() == IP4Addr("2.2.2.2"));
+  REQUIRE(r4.load("2.2.2.2.2") == false);
+  REQUIRE(r4.load("2.2.2.2-fe80:20c::29ff:feae:5587::1c33") == false);
+};
+
+TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") {
   IPEndpoint ep;
   std::string_view addr_1{"[ffee::24c3:3349:3cee:143]:8080"};
   std::string_view addr_2{"172.17.99.231:23995"};
@@ -172,6 +145,7 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]")
   REQUIRE(w.view() == "ffee:0000:0000:0000:24c3:3349:3cee:0143");
   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);
@@ -217,10 +191,6 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]")
   REQUIRE(w.view() == "172. 17. 99.231");
   w.clear().print("{::=a}", ep);
   REQUIRE(w.view() == "172.017.099.231");
-  //  w.clear().print("{}", swoc::bwf::Hex_Dump(ep));
-  //  REQUIRE(w.view() == "ac1163e7");
-  //  w.clear().print("{:#X}", swoc::bwf::Hex_Dump(ep));
-  //  REQUIRE(w.view() == "0XAC1163E7");
 
 #if 0
   // Documentation examples
@@ -256,3 +226,277 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]")
   REQUIRE(w.view() == "ffee00000000000024c333493cee0143");
 #endif
 }
+
+// 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;
+  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();
+    }
+  };
+
+  REQUIRE(space.count() == 0);
+
+  space.mark({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);
+
+  result = space.find({"172.17.0.97"});
+  REQUIRE(result == nullptr);
+
+  space.mark({IP4Addr("172.16.0.12"), IP4Addr("172.16.0.25")}, 2);
+
+  result = space.find({"172.16.0.21"});
+  REQUIRE(result != nullptr);
+  REQUIRE(*result == 2);
+  REQUIRE(space.count() == 3);
+
+  space.clear();
+  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"};
+
+  space.blend(r_1, 0x1, BF);
+  REQUIRE(space.count() == 1);
+  REQUIRE(nullptr == space.find(r_2.min()));
+  REQUIRE(nullptr != space.find(r_1.min()));
+  REQUIRE(nullptr != space.find(r_1.max()));
+
+  space.blend(r_2, 0x2, BF);
+  REQUIRE(space.count() == 2);
+  REQUIRE(nullptr != space.find(r_1.min()));
+  payload = space.find(r_2.min());
+  REQUIRE(payload != nullptr);
+  REQUIRE(*payload == 0x2);
+  payload = space.find(r_2.max());
+  REQUIRE(payload != nullptr);
+  REQUIRE(*payload == 0x2);
+
+  space.blend(r_3, 0x4, BF);
+  dump(space);
+  REQUIRE(space.count() == 5);
+  payload = space.find(r_2.min());
+  REQUIRE(payload != nullptr);
+  REQUIRE(*payload == 0x6);
+
+  payload = space.find(r_3.min());
+  REQUIRE(payload != nullptr);
+  REQUIRE(*payload == 0x4);
+
+  payload = space.find(r_1.max());
+  REQUIRE(payload != nullptr);
+  REQUIRE(*payload == 0x5);
+
+  space.blend({r_2.min(), r_3.max()}, 0x6, BF);
+  dump(space);
+  REQUIRE(space.count() == 4);
+}
+
+#if 1
+TEST_CASE("IP Space YNETDB", "[libswoc][ipspace][ynetdb]") {
+  std::set<std::string_view> Locations;
+  std::set<std::string_view> Owners;
+  std::set<std::string_view> Descriptions;
+  swoc::MemArena arena;
+  auto Localize = [&](TextView const&view) -> TextView {
+    auto span = arena.alloc(view.size() + 1).rebind<char>();
+    memcpy(span, view);
+    span[view.size()] = '\0';
+    return span.view();
+  };
+
+  struct Payload {
+    std::string_view _colo;
+    std::string_view _owner;
+    std::string_view _descr;
+    unsigned int _internal_p : 1;
+    unsigned int _prod_p : 1;
+    unsigned int _corp_p : 1;
+    unsigned int _flakey_p : 1;
+    unsigned int _secure_p : 1;
+
+    Payload() {
+      _internal_p = _prod_p = _corp_p = _flakey_p = _secure_p = false;
+    }
+
+    bool operator==(Payload const&that) {
+      return
+          this->_colo == that._colo &&
+          this->_owner == that._owner &&
+          this->_descr == that._descr &&
+          this->_corp_p == that._corp_p &&
+          this->_internal_p == that._internal_p &&
+          this->_prod_p == that._prod_p &&
+          this->_flakey_p == that._flakey_p &&
+          this->_secure_p == that._secure_p;
+    }
+  };
+
+  using Space = swoc::IPSpace<Payload>;
+
+  Space space;
+
+  std::error_code ec;
+  swoc::file::path csv{"/tmp/ynetdb.csv"};
+
+  // Force these so I can easily change the set of tests.
+  auto start            = std::chrono::high_resolution_clock::now();
+  auto delta = std::chrono::high_resolution_clock::now() - start;
+
+  auto file_content{swoc::file::load(csv, ec)};
+  TextView content{file_content};
+
+  delta = std::chrono::high_resolution_clock::now() - start;
+  std::cout << "File load " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count()
+            << "ms" << std::endl;
+
+  start = std::chrono::high_resolution_clock::now();
+  while (content) {
+    Payload payload;
+
+    auto line = content.take_prefix_at('\n');
+    if (line.trim_if(&isspace).empty() || *line == '#') {
+      continue;
+    }
+
+    auto range_token{line.take_prefix_at(',')};
+    auto internal_token{line.take_prefix_at(',')};
+    auto owner_token{line.take_prefix_at(',')};
+    auto colo_token{line.take_prefix_at(',')};
+    auto flags_token{line.take_prefix_at(',')};
+    auto descr_token{line.take_prefix_at(',')};
+
+    if (auto spot{Owners.find(owner_token)}; spot != Owners.end()) {
+      payload._owner = *spot;
+    } else {
+      payload._owner = Localize(owner_token);
+      Owners.insert(payload._owner);
+    }
+
+    if (auto spot{Descriptions.find(owner_token)}; spot != Descriptions.end()) {
+      payload._descr = *spot;
+    } else {
+      payload._descr = Localize(owner_token);
+      Descriptions.insert(payload._descr);
+    }
+
+    if (auto spot{Locations.find(owner_token)}; spot != Locations.end()) {
+      payload._colo = *spot;
+    } else {
+      payload._colo = Localize(owner_token);
+      Locations.insert(payload._colo);
+    }
+
+    static constexpr TextView INTERNAL_TAG{"yahoo"};
+    static constexpr TextView FLAKEY_TAG{"flakey"};
+    static constexpr TextView PROD_TAG{"prod"};
+    static constexpr TextView CORP_TAG{"corp"};
+    static constexpr TextView SECURE_TAG { "secure" };
+
+    if (0 == strcasecmp(internal_token, INTERNAL_TAG)) {
+      payload._internal_p = true;
+    }
+
+    if (flags_token != "-"_sv) {
+      while (flags_token) {
+        auto key = flags_token.take_prefix_at(';').trim_if(&isspace);
+        if (0 == strcasecmp(key, FLAKEY_TAG)) {
+          payload._flakey_p = true;
+        } else if (0 == strcasecmp(key, PROD_TAG)) {
+          payload._prod_p = true;
+        } else if (0 == strcasecmp(key, CORP_TAG)) {
+          payload._corp_p = true;
+        } else if (0 == strcasecmp(key, SECURE_TAG)) {
+          payload._secure_p = true;
+        } else {
+          throw std::domain_error("Bad flag tag");
+        }
+      }
+    }
+
+    auto BF = [](Payload&lhs, Payload const&rhs) -> bool {
+      if (&lhs != &rhs) {
+        std::cout << "Overlap " << lhs._descr << " with " << rhs._descr << std::endl;
+      }
+      return true;
+    };
+    swoc::LocalBufferWriter<1024> bw;
+    swoc::IPRange range;
+    if (range.load(range_token)) {
+      space.blend(range, payload, BF);
+    } else {
+      std::cout << "Failed to parse range " << range_token << std::endl;
+    }
+  }
+  delta = std::chrono::high_resolution_clock::now() - start;
+  std::cout << "Space load " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count()
+            << "ms" << std::endl;
+
+  static constexpr size_t N_LOOPS = 1000000;
+  start = std::chrono::high_resolution_clock::now();
+  for ( size_t idx = 0 ; idx < N_LOOPS ; ++idx ) {
+    space.find(IP4Addr(static_cast<in_addr_t>(idx * 2500)));
+  }
+  delta = std::chrono::high_resolution_clock::now() - start;
+  std::cout << "Space IP4 lookup " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count()
+            << "ms" << std::endl;
+
+}
+#endif


(trafficserver-libswoc) 06/08: Documentation update for IP Networking.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3f35e4b8be14306d4e858b720aeed075e79c92d0
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Wed Dec 4 13:20:08 2019 -0600

    Documentation update for IP Networking.
---
 doc/Makefile                  |   4 +-
 doc/code/BW_Format.en.rst     |   4 +-
 doc/code/IPSpace.en.rst       | 127 ++++++++++++++++++++++++++++++++++++++++--
 doc/index.rst                 |   1 +
 swoc++/include/swoc/swoc_ip.h |  36 ++++++++++--
 5 files changed, 160 insertions(+), 12 deletions(-)

diff --git a/doc/Makefile b/doc/Makefile
index cde5d25..047a4dc 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -22,7 +22,8 @@ $(IMAGEDIR)/%.svg : $(UMLDIR)/%.uml
 
 help:
 	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
+	@echo "  html       to make standalone HTML files."
+	@echo "  publish    to build the documentation clean, ready to publish."
 
 uml:
 	@if [ ! -d uml ] ; then mkdir uml; fi
@@ -38,6 +39,7 @@ html: static uml ext/local-config.py reference
 clean:
 	-rm -rf html warn.log
 	-rm -rf $(BUILDDIR)/doctrees $(BUILDDIR)/html $(BUILDDIR)/dirhtml $(BUILDDIR)/singlehtml
+	-rm -rf doxygen
 
 publish: clean doxygen html
 
diff --git a/doc/code/BW_Format.en.rst b/doc/code/BW_Format.en.rst
index 1d8ec08..712da59 100644
--- a/doc/code/BW_Format.en.rst
+++ b/doc/code/BW_Format.en.rst
@@ -489,8 +489,8 @@ Specific types
    :code:`#include <swoc/bwf_ip.h>`
 
    The IP address is printed. Fill is used to fill in address segments if provided, not to the
-   minimum width if specified. :class:`IpEndpoint` and :class:`IpAddr` are supported with the same
-   formatting. The formatting support in this case is extensive because of the commonality and
+   minimum width if specified. :libswoc:`IPEndpoint` and :libswoc:`IPAddr` are supported with the
+   same formatting. The formatting support in this case is extensive because of the commonality and
    importance of IP address data.
 
    Type overrides
diff --git a/doc/code/IPSpace.en.rst b/doc/code/IPSpace.en.rst
index 353a171..832d799 100644
--- a/doc/code/IPSpace.en.rst
+++ b/doc/code/IPSpace.en.rst
@@ -16,15 +16,18 @@
 .. default-domain:: cpp
 .. highlight:: cpp
 
-********
-IP Space
-********
+*************
+IP Networking
+*************
 
 Synopsis
 ********
 
 :code:`#include <swoc/swoc_ip.h>`
 
+Usage
+*****
+
 .. class:: IPEndpoint
 
    :libswoc:`Reference documentation <swoc::IPEndpoint>`.
@@ -32,8 +35,122 @@ Synopsis
 This library is for storing and manipulating IP addresses as data. It has no support for actual
 network operations.
 
-Usage
-*****
+IPAddr
+======
+
+The classes :libswoc:`swoc::IPAddr`, :libswoc:`swoc::IP4Addr`, and :libswoc:`swoc::IP6Addr` are used
+to hold IP addresses. :code:`IP4Addr` and :code:`IP6Addr` are family specific and hold
+(respectively) IPv4 and IPv6 addresses. :code:`IPAddr` acts as a union of these two types along with
+an IP family specifier that indicates the type of address contained. The type specific classes
+provide performance and storage benefits when the type of the address is known or forced, while
+:code:`IPAddr` provides a generic type useful for interfaces.
+
+These classes provide support for parsing and formatting IP addresses. The constructor can take
+a string and, if a valid address, will initialize the instance to that address. The downside is
+there is no indication of failure other than the instance initializing to the zero or "any"
+address. This can be reasonable in situations where those addresses are not valid either. However
+in general the :libswoc:`swoc::IPAddr::load` method should be used, which both initializes the
+instance and provides an indication of whether the input was valid.
+
+IPRange
+=======
+
+The classes :libswoc:`swoc::IPRange`, :libswoc:`swoc::IP4Range`, and :libswoc:`swoc::IP6Range` are
+used to hold ranges of IP addresses. :code:`IP4Range` and :code:`IP6Range` are family specific and
+hold (respectively) IPv4 and IPv6 addresses. :code:`IPAddr` acts as a union of these two types along
+with an IP family specifier that indicates the type of address contained. The type specific classes
+provide performance and storage benefits when the type of the address is known or forced, while
+:code:`IPRange` provides a generic type useful for interfaces. Note that an :code:`IPRange` holds a
+range of addresses of a single family, it can never hold a range that is of mixed families.
+
+These classes provide support for parsing and formatting ranges of IP adddresses. The parsing logic
+accepts three forms of a range. In all cases the lower and upper limits of the range must be the
+same IP address family.
+
+Range
+   Two addresses, separated by a dash ("-") character. E.g.
+
+      172.26.13.4-172.27.12.9
+
+Network
+   An address and a CIDR based mask, separated by a slash ("/") character. E.g.
+
+      1337:0:0:ded:BEEF::/48
+
+Singleton
+   A single IP address, which is interpreted as a range of size 1.
+
+Such a string can be passed to the constructor, which will initialize to the corresponding range if
+properly formatted, otherwise the range will be default constructed to an invalid range. There is
+also the :libswoc:`swoc::IPRange::load` method which returns a :code:`bool` to indicate if the
+parsing was successful.
+
+IPSpace
+=======
+
+The :libswoc:`swoc::IPSpace` class is designed as a container for ranges of IP addresses. Lookup is
+done by single addresses. Conceptually, for each possible IP address there is a *payload*. Populating
+the container is done by applying a specific payload to a range of addresses. After populating an
+IP address can be looked up to find the corresponding payload.
+
+The payload is a template argument to the class, as with standard containers. There is no template
+argument for the key, as that is always an IP address.
+
+Applying payloads to the space is analogized to painting, each distinct payload considered a different
+"color" for an address. There are several methods used to paint the space, depending on the desired
+effect on existing payloads.
+
+mark
+   :libswoc:`swoc::IPSpace::mark` applies the :arg:`payload` to the range, replacing any existing
+   payload present. This is modeled on the "painter's algorithm" where the most recent coloring
+   replaces any prior colors in the region.
+
+fill
+   :libswoc:`swoc::IPSpace::fill` applies the :arg:`payload` to the range but only where there is not
+   already a payload. This is modeled on "backfilling" a background. This is useful for "first match"
+   logic, as which ever payload is first put in to the container will remain.
+
+blend
+   :libswoc:`swoc::IPSpace::blend` applies the :arg:`payload` to the range by combining ("blending")
+   the payloads. The result of blending can be "uncolored" which results in those addresses being
+   removed from the space. This is useful for applying different properties in sequence, where the
+   result is a combination of the properties.
+
+Blend
++++++
+
+The :libswoc:`swoc::IPSpace::blend` method requires a range and a "blender", which is a functor
+that combines two :code:`PAYLOAD` instances. The signature is ::
+
+  bool blender(PAYLOAD & lhs, PAYLOAD const& rhs)
+
+The method is modeled on C++ `compound assignment operators
+<https://en.cppreference.com/w/cpp/language/operator_assignment#Builtin_compound_assignment>`__. If
+the blend operation is thought of as the "@" operator, then the blend functor performs :code:`lhs @=
+rhs`. That is, :arg:`lhs` is modified to be the combination of :arg:`lhs` and :arg`rhs`. :arg:`lhs`
+is always the previous payload already in the space, and :arg:`rhs` is the :arg:`payload` argument
+to the :code:`blend` method. The internal logic handles copying the payload instances as needed.
+
+The return value indicates whether the combined result in :arg:`lhs` is a valid payload or not. If
+it is the method should return :code:`true`. In general most implementations will :code:`return
+true;` in all cases. If the method returns :code:`false` then the address(es) for the combined
+payload are removed from the container. This allows payloads to be "unblended", for one payload to
+cancel out another, or to do selective erasing of ranges.
+
+As an example, consider the case where the payload is a bitmask. It might be reasonable to keep
+empty bitmasks in the container, but it would be reasonble to decide the empty bitmask and any
+address mapped to it should removed entirely from the container. In such a case, a blender that
+clears bits in the payloads should return :code:`false` when the result is the empty bitmask.
+
+Similarly, if the goal is to remove ranges that have a specific payload, then a blender that returns
+:code:`false` if :arg:`lhs` matches that specific payload and :code:`true` if not, should be used.
+
+There is a small implementation wrinkle, however. Because the blending logic cannot assume the
+result of blending a payload with an equal payload results in the same payload, it must do a test to
+determine the result. I.e. if :arg:`lhs == rhs` for the arguments, it cannot be assumed that after
+the blend :arg:`lhs` remains unchanged (consider the bitmask unsetting blender earlier). The blender
+may want to return something special in this case, which can be detected by comparing :code:`&lhs ==
+&rhs`. If true then this is the test case, otherwise it is not.
 
 History
 *******
diff --git a/doc/index.rst b/doc/index.rst
index ffe0eb6..415634d 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -26,6 +26,7 @@ unrelated projects. Hence this library. I hope you find it as useful as I have.
    :maxdepth: 1
 
    preface.en
+   building.en
    code/BufferWriter.en
    code/BW_Format.en
    code/meta.en
diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h
index 2ba59dd..e504e15 100644
--- a/swoc++/include/swoc/swoc_ip.h
+++ b/swoc++/include/swoc/swoc_ip.h
@@ -525,6 +525,14 @@ public:
     return f == _family;
   }
 
+  /** Load the range from @a text.
+   *
+   * @param text Range specifier in text format.
+   * @return @c true if @a text was successfully parsed, @c false if not.
+   *
+   * A successful parse means @a this was loaded with the specified range. If not the range is
+   * marked as invalid.
+   */
   bool load(std::string_view const& text);
 
   IPAddr min() const;
@@ -633,6 +641,16 @@ public:
    */
   self_type & mark(IP4Range const &r, PAYLOAD const &payload);
 
+  /** Fill the @a range with @a payload.
+   *
+   * @param range Destination range.
+   * @param payload Payload for range.
+   * @return this
+   *
+   * Addresses in @a range are set to have @a payload if the address does not already have a payload.
+   */
+  self_type & fill(IPRange const& range, PAYLOAD const& payload);
+
   /** Blend @a payload in to the @a range.
    *
    * @tparam F Blending functor type (deduced).
@@ -1192,10 +1210,13 @@ template < typename PAYLOAD > auto IPSpace<PAYLOAD>::mark(swoc::IP4Range const &
   return *this;
 }
 
-template<typename PAYLOAD>
-void IPSpace<PAYLOAD>::clear() {
-  _ip4.clear();
-  _ip6.clear();
+template < typename PAYLOAD > auto IPSpace<PAYLOAD>::fill(swoc::IPRange const &range, PAYLOAD const &payload) -> self_type & {
+  if (range.is(AF_INET6)) {
+    _ip6.fill(range, payload);
+  } else if (range.is(AF_INET)) {
+    _ip4.fill(range, payload);
+  }
+  return *this;
 }
 
 template<typename PAYLOAD>
@@ -1209,4 +1230,11 @@ auto IPSpace<PAYLOAD>::blend(IPRange const&range, PAYLOAD const&payload, F&&blen
   return *this;
 }
 
+template<typename PAYLOAD>
+void IPSpace<PAYLOAD>::clear() {
+  _ip4.clear();
+  _ip6.clear();
+}
+
+
 } // namespace swoc


(trafficserver-libswoc) 07/08: Change IPSpace::blend to take an arbitrary type for the blending color.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit eb5adefa80147baf145eca78286667badfb3f020
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Wed Dec 4 15:23:05 2019 -0600

    Change IPSpace::blend to take an arbitrary type for the blending color.
---
 doc/building.en.rst                 | 38 ++++++++++++++++++++++++
 doc/code/IPSpace.en.rst             | 22 +++++++-------
 swoc++/include/swoc/DiscreteRange.h | 58 +++++++++++++++++++++----------------
 swoc++/include/swoc/swoc_ip.h       | 43 +++++++++++++--------------
 unit_tests/test_ip.cc               |  2 +-
 5 files changed, 106 insertions(+), 57 deletions(-)

diff --git a/doc/building.en.rst b/doc/building.en.rst
new file mode 100644
index 0000000..ee0b744
--- /dev/null
+++ b/doc/building.en.rst
@@ -0,0 +1,38 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+   agreements. See the NOTICE file distributed with this work for
+   additional information regarding copyright ownership. The ASF licenses this file to you under the
+   Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+   the License. You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software distributed under the License
+   is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+   or implied. See the License for the specific language governing permissions and limitations under
+   the License.
+
+.. include:: common-defs.rst
+
+.. _building:
+
+Building
+********
+
+The library is usually built using CMake, but is also intended to be buildable as a part for
+`SCons <http://scons.org>`__ using the `Parts <https://pypi.org/project/scons-parts/>` extensions.
+For this reason the overall codebase is split into the library code and the test code. As a library
+only the library code needs to be built, which is the part "swoc++/swoc++.part". For example, to
+get the 1.0.8 release as a library in a larger project, the top level "Sconstruct" would have ::
+
+   Part("swoc++/swoc++.part"
+     , vcs_type=VcsGit(server="github.com"
+       , repository="SolidWallOfCode/libswoc"
+       , tag="1.0.8"))
+
+The object that depends on this library would then have ::
+
+   DependsOn([
+      # ... other dependencies
+      , Component("swoc++")
+      , # ... more dependencies
+   ])
diff --git a/doc/code/IPSpace.en.rst b/doc/code/IPSpace.en.rst
index 832d799..fa2ef35 100644
--- a/doc/code/IPSpace.en.rst
+++ b/doc/code/IPSpace.en.rst
@@ -120,15 +120,19 @@ Blend
 +++++
 
 The :libswoc:`swoc::IPSpace::blend` method requires a range and a "blender", which is a functor
-that combines two :code:`PAYLOAD` instances. The signature is ::
+that blends a :arg:`color` into a :code:`PAYLOAD` instances. The signature is ::
 
-  bool blender(PAYLOAD & lhs, PAYLOAD const& rhs)
+  bool blender(PAYLOAD & payload, U const& color)
+
+The type :code:`U` is that same as the template argument :code:`U` to the method, which must be
+compatible with the second argument to the :code:`blend` method. The argument passed to
+:code:`blender` is the second argument to :code:`blend`.
 
 The method is modeled on C++ `compound assignment operators
 <https://en.cppreference.com/w/cpp/language/operator_assignment#Builtin_compound_assignment>`__. If
 the blend operation is thought of as the "@" operator, then the blend functor performs :code:`lhs @=
 rhs`. That is, :arg:`lhs` is modified to be the combination of :arg:`lhs` and :arg`rhs`. :arg:`lhs`
-is always the previous payload already in the space, and :arg:`rhs` is the :arg:`payload` argument
+is always the previous payload already in the space, and :arg:`rhs` is the :arg:`color` argument
 to the :code:`blend` method. The internal logic handles copying the payload instances as needed.
 
 The return value indicates whether the combined result in :arg:`lhs` is a valid payload or not. If
@@ -138,19 +142,17 @@ payload are removed from the container. This allows payloads to be "unblended",
 cancel out another, or to do selective erasing of ranges.
 
 As an example, consider the case where the payload is a bitmask. It might be reasonable to keep
-empty bitmasks in the container, but it would be reasonble to decide the empty bitmask and any
+empty bitmasks in the container, but it would also be reasonble to decide the empty bitmask and any
 address mapped to it should removed entirely from the container. In such a case, a blender that
 clears bits in the payloads should return :code:`false` when the result is the empty bitmask.
 
 Similarly, if the goal is to remove ranges that have a specific payload, then a blender that returns
 :code:`false` if :arg:`lhs` matches that specific payload and :code:`true` if not, should be used.
 
-There is a small implementation wrinkle, however. Because the blending logic cannot assume the
-result of blending a payload with an equal payload results in the same payload, it must do a test to
-determine the result. I.e. if :arg:`lhs == rhs` for the arguments, it cannot be assumed that after
-the blend :arg:`lhs` remains unchanged (consider the bitmask unsetting blender earlier). The blender
-may want to return something special in this case, which can be detected by comparing :code:`&lhs ==
-&rhs`. If true then this is the test case, otherwise it is not.
+There is a small implementation wrinkle, however, in dealing with unmapped addresses. The
+:arg:`color` is not necessarily a :code:`PAYLOAD` and therefore must be converted in to one. This
+is done by default constructing a :code:`PAYLOAD` instance and then calling :code:`blend` on that
+and the :arg:`color`. If this returns :code:`false` then unmapped addresses will remain unmapped.
 
 History
 *******
diff --git a/swoc++/include/swoc/DiscreteRange.h b/swoc++/include/swoc/DiscreteRange.h
index b8914ff..21d177e 100644
--- a/swoc++/include/swoc/DiscreteRange.h
+++ b/swoc++/include/swoc/DiscreteRange.h
@@ -671,22 +671,24 @@ public:
    */
   self_type &erase(range_type const &range);
 
-  /** Blend a @a payload to a @a range.
+  /** Blend a @a color to a @a range.
    *
-   * @tparam blender Functor to blend payloads.
+   * @tparam F Functor to blend payloads.
+   * @tparam U type to blend in to payloads.
    * @param range Range for blending.
-   * @param payload Payload to blend.
+   * @param color Payload to blend.
    * @return @a this
    *
-   * @a payload is blended to values in @a range. If a value is not present, its payload is set
-   * to @a payload. If the value is present, its payload is set to the blend of the existing payload
-   * and @a payload using @blender. The existing payload is passed as the first argument and @a
-   * payload as the second argument. The functor is expected to update the first argument to be the
-   * blended payload. The function must return a @c bool to indicate whether the blend resulted in
-   * a valid color. If @c false is returned, the blended region is removed from the space.
+   * @a color is blended to values in @a range. If an address in @a range does not have a payload,
+   * its payload is set a default constructed @c PAYLOAD blended with @a color. If such an address
+   * does have a payload, @a color is blended in to that payload using @blender. The existing color
+   * is passed as the first argument and @a color as the second argument. The functor is expected to
+   * update the first argument to be the blended color. The function must return a @c bool to
+   * indicate whether the blend resulted in a valid color. If @c false is returned, the blended
+   * region is removed from the space.
    */
-  template < typename F >
-  self_type &blend(range_type const &range, PAYLOAD const &payload, F && blender);
+  template < typename F, typename U = PAYLOAD >
+  self_type &blend(range_type const &range, U const &color, F && blender);
 
   /** Fill @a range with @a payload.
    *
@@ -699,7 +701,12 @@ public:
    */
   self_type &fill(range_type const &range, PAYLOAD const &payload);
 
-  PAYLOAD * find(METRIC const &addr);
+  /** Find the payload at @a metric.
+   *
+   * @param metric The metric for which to search.
+   * @return The payload for @a metric if found, @c nullptr if not found.
+   */
+  PAYLOAD * find(METRIC const &metric);
 
   /// @return The number of distinct ranges.
   size_t count() const;
@@ -812,17 +819,17 @@ DiscreteSpace<METRIC, PAYLOAD>::head() -> Node *{
 
 template <typename METRIC, typename PAYLOAD>
 PAYLOAD *
-DiscreteSpace<METRIC, PAYLOAD>::find(METRIC const &addr) {
+DiscreteSpace<METRIC, PAYLOAD>::find(METRIC const &metric) {
   auto n = _root; // current node to test.
   while (n) {
-    if (addr < n->min()) {
-      if (n->_hull.contains(addr)) {
+    if (metric < n->min()) {
+      if (n->_hull.contains(metric)) {
         n = n->left();
       } else {
         return nullptr;
       }
-    } else if (n->max() < addr) {
-      if (n->_hull.contains(addr)) {
+    } else if (n->max() < metric) {
+      if (n->_hull.contains(metric)) {
         n = n->right();
       } else {
         return nullptr;
@@ -1147,16 +1154,17 @@ DiscreteSpace<METRIC, PAYLOAD>::fill(DiscreteSpace::range_type const &range, PAY
 }
 
 template<typename METRIC, typename PAYLOAD>
-template<typename F>
+template<typename F, typename U>
 auto
-DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, PAYLOAD const&payload
+DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U const& color
                                       , F &&blender) -> self_type & {
-  // Do a base check for the color to use on unmapped values. If self blending on @a payload
+  // Do a base check for the color to use on unmapped values. If self blending on @a color
   // is @c false, then do not color currently unmapped values.
-  PAYLOAD plain_color { payload };
-  bool plain_color_p = blender(plain_color, plain_color);
-  // Used to hold a temporary blended node - @c release if put in space, otherwise cleaned up.
+  auto plain_color = PAYLOAD();
+  bool plain_color_p = blender(plain_color, color);
+
   auto node_cleaner = [&] (Node * ptr) -> void { _fa.destroy(ptr); };
+  // 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.
@@ -1246,7 +1254,7 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, PAY
     // 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.
     unique_node fill { _fa.make(n->min(), max, n->payload()), node_cleaner };
-    bool fill_p = blender(fill->payload(), payload); // fill or clear?
+    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;
@@ -1262,7 +1270,7 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, PAY
           return *this;
         }
       } else {
-        // PROBLEM - not collapsing into @a pred(n) when it's adjacent / matching payload.
+        // 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()) {
diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h
index e504e15..f440caa 100644
--- a/swoc++/include/swoc/swoc_ip.h
+++ b/swoc++/include/swoc/swoc_ip.h
@@ -651,35 +651,36 @@ public:
    */
   self_type & fill(IPRange const& range, PAYLOAD const& payload);
 
-  /** Blend @a payload in to the @a range.
+  /** Blend @a color in to the @a range.
    *
    * @tparam F Blending functor type (deduced).
+   * @tparam U Data to blend in to payloads.
    * @param range Target range.
-   * @param payload Payload for @a range.
+   * @param color Data to blend in to existing payloads in @a range.
    * @param blender Blending functor.
    * @return @a this
    *
-   * @a blender is required to have the signature <tt>void(PAYLOAD& lhs , PAYLOAD CONST&rhs)</tt>.
-   * It must act as a compound assignment operator, blending @a B into @a A. That is, if the
-   * result of blending B in to A is defined as "A @ B" for the binary operator "@", then
-   * @a blender computes "A @= B".
+   * @a blender is required to have the signature <tt>void(PAYLOAD& lhs , U CONST&rhs)</tt>. It must
+   * act as a compound assignment operator, blending @a rhs into @a lhs. That is, if the result of
+   * blending @a rhs in to @a lhs is defined as "lhs @ rhs" for the binary operator "@", then @a
+   * blender computes "lhs @= rhs".
    *
-   * Every address in @a range is assigned a payload. If the address does not already have a
-   * payload, it is assigned @a payload. If the address has a payload of A it is updated by
-   * invoking @a blender on the existing payload and @a payload.
+   * Every address in @a range is assigned a payload. If the address does not already have a color,
+   * it is assigned the default constructed @c PAYLOAD blended with @a color. If the address has a
+   * color of A it is updated by invoking @a blender on the existing payload and @a color.
    */
-  template < typename F >
-  self_type & blend(IPRange const& range, PAYLOAD const& payload, F && blender);
+  template < typename F , typename U = PAYLOAD >
+  self_type & blend(IPRange const& range, U const& color, F && blender);
 
-  template < typename F >
-  self_type & blend(IP4Range const& range, PAYLOAD const& payload, F && blender) {
-    _ip4.blend(range, payload, blender);
+  template < typename F , typename U = PAYLOAD >
+  self_type & blend(IP4Range const& range, U const& color, F && blender) {
+    _ip4.blend(range, color, blender);
     return *this;
   }
 
-  template < typename F >
-  self_type & blend(IP6Range const& range, PAYLOAD const& payload, F && blender) {
-    _ip6.blend(range, payload, blender);
+  template < typename F , typename U = PAYLOAD >
+  self_type & blend(IP6Range const& range, U const& color, F && blender) {
+    _ip6.blend(range, color, blender);
     return *this;
   }
 
@@ -1220,12 +1221,12 @@ template < typename PAYLOAD > auto IPSpace<PAYLOAD>::fill(swoc::IPRange const &r
 }
 
 template<typename PAYLOAD>
-template<typename F>
-auto IPSpace<PAYLOAD>::blend(IPRange const&range, PAYLOAD const&payload, F&&blender) -> self_type &{
+template<typename F, typename U >
+auto IPSpace<PAYLOAD>::blend(IPRange const&range, U const&color, F&&blender) -> self_type & {
   if (range.is(AF_INET)) {
-    _ip4.blend(range, payload, blender);
+    _ip4.blend(range, color, blender);
   } else if (range.is(AF_INET6)) {
-    _ip6.blend(range, payload, blender);
+    _ip6.blend(range, color, blender);
   }
   return *this;
 }
diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc
index b7a1aee..953c2cf 100644
--- a/unit_tests/test_ip.cc
+++ b/unit_tests/test_ip.cc
@@ -472,7 +472,7 @@ TEST_CASE("IP Space YNETDB", "[libswoc][ipspace][ynetdb]") {
     }
 
     auto BF = [](Payload&lhs, Payload const&rhs) -> bool {
-      if (&lhs != &rhs) {
+      if (! lhs._colo.empty()) {
         std::cout << "Overlap " << lhs._descr << " with " << rhs._descr << std::endl;
       }
       return true;


(trafficserver-libswoc) 08/08: Fix version, version update script.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6c3a3b894a39f39f3a75fe2e0ef4af800ef0689a
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Tue Jan 21 17:23:44 2020 -0600

    Fix version, version update script.
---
 swoc++/include/swoc/swoc_version.h | 5 +++--
 swoc++/swoc++.part                 | 2 +-
 tools/update-version.sh            | 7 ++++---
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/swoc++/include/swoc/swoc_version.h b/swoc++/include/swoc/swoc_version.h
index 1800d03..b3f9963 100644
--- a/swoc++/include/swoc/swoc_version.h
+++ b/swoc++/include/swoc/swoc_version.h
@@ -37,6 +37,7 @@
 
 namespace swoc
 {
-static constexpr unsigned MAJOR_VERSION = 1 static constexpr unsigned MINOR_VERSION = 0 static constexpr unsigned POINT_VERSION = 9
-
+static constexpr unsigned MAJOR_VERSION = 1;
+static constexpr unsigned MINOR_VERSION = 0;
+static constexpr unsigned POINT_VERSION = 9;
 } // namespace swoc
diff --git a/swoc++/swoc++.part b/swoc++/swoc++.part
index 9b3bb5d..8d34df8 100644
--- a/swoc++/swoc++.part
+++ b/swoc++/swoc++.part
@@ -1,5 +1,5 @@
 Import("*")
-PartVersion("1.0.8")
+PartVersion("1.0.9")
 PartName("swoc++")
 
 files = [
diff --git a/tools/update-version.sh b/tools/update-version.sh
index 2413f8d..c12569b 100644
--- a/tools/update-version.sh
+++ b/tools/update-version.sh
@@ -6,11 +6,12 @@ if [ -z "$3" ] ; then
 fi
 
 # Header
-sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(MAJOR_VERSION *= *\).*\$/\\1$1/"
-sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(MINOR_VERSION *= *\).*\$/\\1$2/"
-sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(POINT_VERSION *= *\).*\$/\\1$3/"
+sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(MAJOR_VERSION *= *\).*\$/\\1$1;/"
+sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(MINOR_VERSION *= *\).*\$/\\1$2;/"
+sed -i swoc++/include/swoc/swoc_version.h --expr "s/\(POINT_VERSION *= *\).*\$/\\1$3;/"
 
 sed -i doc/conf.py --expr "s/release = .*\$/release = \"$1.$2.$3\"/"
 sed -i doc/Doxyfile --expr "s/\(PROJECT_NUMBER *= *\).*\$/\\1\"$1.$2.$3\"/"
 
 sed -i swoc++/CMakeLists.txt --expr "s/\(LIBSWOC_VERSION *\)\"[^\"]*\"/\\1\"$1.$2.$3\"/"
+sed -i swoc++/swoc++.part --expr "s/PartVersion(\"[0-9.]*\")/PartVersion(\"$1.$2.$3\")/"


(trafficserver-libswoc) 01/08: Lexicon: Change default handlers to make them available for constant instances.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 968de363c1697a521772d550f97664690758f264
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Fri Oct 18 18:40:14 2019 -0500

    Lexicon: Change default handlers to make them available for constant instances.
---
 swoc++/include/swoc/Lexicon.h | 436 +++++++++++++++++-------------------------
 unit_tests/test_Lexicon.cc    |  37 +++-
 2 files changed, 204 insertions(+), 269 deletions(-)

diff --git a/swoc++/include/swoc/Lexicon.h b/swoc++/include/swoc/Lexicon.h
index 96351d0..b66360e 100644
--- a/swoc++/include/swoc/Lexicon.h
+++ b/swoc++/include/swoc/Lexicon.h
@@ -23,6 +23,8 @@
 #include <tuple>
 #include <functional>
 #include <array>
+#include <variant>
+
 #include "swoc/IntrusiveHashMap.h"
 #include "swoc/MemArena.h"
 #include "swoc/bwf_base.h"
@@ -38,6 +40,8 @@ namespace detail
    * @param fmt Format string.
    * @param args Arguments to format string.
    * @return r-value reference to a @c std::string containing the formatted string.
+   *
+   * This is used when throwing exceptions.
    */
   template <typename... Args>
   std::string
@@ -62,6 +66,12 @@ namespace detail
     @c TRUE would always be "true", while converting any of "true", "1", "yes", or "enable" would
     yield @c TRUE. This is convenient for parsing configurations to be more tolerant of input.
 
+    The constructors are a bit baroque, but this is necessary in order to be able to declare
+    constant instances of the @c Lexicon. If this isn't necessary, everything that can be done via
+    the constructor can be done with other methods. The implementation of the constructors consists
+    entirely of calls to @c define and @c set_default, the only difference is these methods can
+    be called on a @c const instance from there.
+
     @note All names and value must be unique across the Lexicon. All name comparisons are case
     insensitive.
  */
@@ -73,34 +83,76 @@ protected:
   struct Item;
 
 public:
-  /// Used for initializer lists that have just a primary value.
-  using Pair = std::tuple<E, std::string_view>;
-  /// A function to be called if a value is not found.
+  /** A function to be called if a value is not found to provide a default name.
+   * @param value The value.
+   * @return A name for the value.
+   *
+   * The name is return by view and therefore managing the lifetime of the name is problematic.
+   * Generally it should be process lifetime, unless some other shorter lifetime can be managed
+   * without a destructor being called. Unfortunately this can't be done any better without
+   * imposing memory management costs on normal use.
+   */
   using UnknownValueHandler = std::function<std::string_view(E)>;
-  /// A function to be called if a name is not found.
+
+  /** A function to be called if a name is not found, to provide a default value.
+   * @param name The name
+   * @return An enumeration value.
+   *
+   * The @a name is provided and a value in the enumeration type is expected.
+   */
   using UnknownNameHandler = std::function<E(std::string_view)>;
 
+  /** A default handler.
+   *
+   * This handles providing a default value or name for a missing name or value.
+   */
+  using DefaultHandler = std::variant<std::monostate, E, std::string_view, UnknownNameHandler, UnknownValueHandler>;
+
+  /// Used for initializer lists that have just a primary value.
+  using Pair = std::tuple<E, std::string_view>;
+
   /// Element of an initializer list that contains secondary names.
   struct Definition {
     const E &value;                                       ///< Value for definition.
     const std::initializer_list<std::string_view> &names; ///< Primary then secondary names.
   };
 
-  /// Template argument carrying struct.
-  /// @note Needed to pass a compile time constant to a constructor as compile time constant.
-  template <E e> struct Require {
-  };
-
   /// Construct empty instance.
   Lexicon();
-  /// Construct with secondary names.
-  explicit Lexicon(const std::initializer_list<Definition> &items);
-  /// Construct with primary names only.
-  explicit Lexicon(const std::initializer_list<Pair> &items);
-  /// Construct and verify the number of definitions.
-  template <E e> Lexicon(const Require<e> &, const std::array<Definition, static_cast<size_t>(e)> &defines);
-  /// Construct and verify the number of pairs.
-  template <E e> Lexicon(const Require<e> &, const std::array<Pair, static_cast<size_t>(e)> &defines);
+
+  /** Construct with names, possible secondary values, and optional default handlers.
+   *
+   * @param items A list of initializers, each of which is a name and a list of values.
+   * @param handler_1 A default handler.
+   * @param handler_2 A default hander.
+   *
+   * Each item in the intializers must be a @c Definition, that is a name and a list of values.
+   * The first value is the primary value and is required. Subsequent values are optional
+   * and become secondary values.
+   *
+   * The default handlers are optional can be be omitted. If so, exceptions are thrown when values
+   * or names not in the @c Lexicon are used. See @c set_default for more details.
+   *
+   * @see set_default.
+   */
+  explicit Lexicon(const std::initializer_list<Definition> &items, DefaultHandler handler_1 = DefaultHandler{},
+                   DefaultHandler handler_2 = DefaultHandler{});
+
+  /** Construct with names / value pairs, and optional default handlers.
+   *
+   * @param items A list of initializers, each of which is a name and a list of values.
+   * @param handler_1 A default handler.
+   * @param handler_2 A default hander.
+   *
+   * Each item in the intializers must be a @c Pair, that is a name and a value.
+   *
+   * The default handlers are optional can be be omitted. If so, exceptions are thrown when values
+   * or names not in the @c Lexicon are used. See @c set_default for more details.
+   *
+   * @see set_default.
+   */
+  explicit Lexicon(const std::initializer_list<Pair> &items, DefaultHandler handler_1 = DefaultHandler{},
+                   DefaultHandler handler_2 = DefaultHandler{});
 
   /** Get the name for a @a value.
    *
@@ -124,45 +176,46 @@ public:
   /// Define a value and names.
   /// <tt>lexicon.define(Value, { primary, [secondary, ...] });</tt>
   self_type &define(E value, const std::initializer_list<std::string_view> &names);
-  self_type &define(const Pair &pair);
-  self_type &define(const Definition &init);
 
-  /** Set a default @a value.
+  /** Define a name, value pair.
    *
-   * @param value Value to return if a name is not found.
-   * @return @c *this
+   * @param pair A @c Pair of the name and value to define.
+   * @return @a this.
    */
-  self_type &set_default(E value);
+  self_type &define(const Pair &pair);
 
-  /** Set a default @a name.
+  /** Define a name with a primary and secondary values.
    *
-   * @param name Name to return if a value is not found.
-   * @return @c *this
+   * @param init The @c Definition with the name and values.
+   * @return @a this.
    *
-   * @note The @a name is copied to local storage.
+   * This defines the name, with the first value in the value list becoming the primary value
+   * and subsequent values (if any) being the secondary values. A primary value is required but
+   * secondary values are not. This is to make it possible to define all values in this style
+   * even if some do not have secondary values.
    */
-  self_type &set_default(std::string_view name);
+  self_type &define(const Definition &init);
 
-  /** Set a default @a handler for names that are not found.
+  /** Set default handler.
    *
-   * @param handler Function to call with a name that was not found.
-   * @return @c this
+   * @param def The handler.
+   * @return @a this.
    *
-   * @a handler is passed the name that was not found as a @c std::string_view and must return a
-   * value which is then returned to the caller.
-   */
-  self_type &set_default(const UnknownNameHandler &handler);
-
-  /** Set a default @a handler for values that are not found.
+   * The @a handler can be of various types.
    *
-   * @param handler Function to call with a value that was not found.
-   * @return @c *this
+   * - An enumeration value. This sets the default value handler to return that value for any
+   *   name not found.
    *
-   * @a handler is passed the value that was not found and must return a name as a @c std::string_view.
-   * Caution must be used because the returned name must not leak and must be thread safe. The most
-   * common use would be for logging bad values.
+   * - A @c string_view. The sets the default name handler to return the @c string_view as the
+   *   name for any value not found.
+   *
+   * - A @c DefaultNameHandler. This is a functor that takes an enumeration value parameter and
+   *   returns a @c string_view as the name for any value that is not found.
+   *
+   * - A @c DefaultValueHandler. This is a functor that takes a name as a @c string_view and returns
+   *   an enumeration value as the value for any name that is not found.
    */
-  self_type &set_default(const UnknownValueHandler &handler);
+  self_type &set_default(DefaultHandler const &handler);
 
   /// Get the number of values with definitions.
   size_t count() const;
@@ -205,89 +258,74 @@ public:
     friend Lexicon;
   };
 
-  // There is no modifying elements in the Lexicon so only constant iteration.
+  // Only constant iteratior allowed, values cannot be modified.
   using iterator = const_iterator;
 
   const_iterator begin() const;
   const_iterator end() const;
 
 protected:
-  // Because std::variant is broken up through clang 6, we have to do something uglier.
-  //  using NameDefault  = std::variant<std::monostate, std::string_view, UnknownValueHandler>;
-  //  using ValueDefault = std::variant<std::monostate, E, UnknownNameHandler>;
-
-  /// Type marker for internal variant.
-  enum class Content {
-    NIL,    ///< Nothing, not set.
-    SCALAR, ///< A specific value/name.
-    HANDLER ///< A function
-  };
-
-  /// Default (no value) struct for variant initialization.
-  struct NilValue {
-  };
-
-  /// Handler for values that are not in the Lexicon.
-  struct NameDefault {
-    using self_type = NameDefault; ///< Self reference type.
-
-    NameDefault() = default; ///< Default constructor.
-    ~NameDefault();          ///< Destructor.
-
-    /** Set the handler to return a fixed value.
-     *
-     * @param name Name to return.
-     * @return @a this
-     */
-    self_type &operator=(std::string_view name);
-
-    /** Set the handler to call a function to compute the default name.
-     *
-     * @param handler Handler called to compute the name.
-     * @return @a this
-     */
-    self_type &operator=(const UnknownValueHandler &handler);
-
-    /** Compute the default name for @a value.
-     *
-     * @param value Value without a name.
-     * @return A name for that value.
-     */
-    std::string_view operator()(E value) const;
+  /// Handle providing a default name.
+  using NameDefault = std::variant<std::monostate, std::string_view, UnknownValueHandler>;
+  /// Handle providing a default value.
+  using ValueDefault = std::variant<std::monostate, E, UnknownNameHandler>;
+
+  /// Visitor functor for handling @c NameDefault.
+  struct NameDefaultVisitor {
+    E _value;
+
+    std::string_view
+    operator()(std::monostate const &) const
+    {
+      throw std::domain_error(detail::what("Lexicon: invalid enumeration value {}", static_cast<int>(_value)).data());
+    }
 
-    /// Internal clean up, needed for assignment and destructor.
-    self_type &destroy();
+    std::string_view
+    operator()(std::string_view const &name) const
+    {
+      return name;
+    }
 
-    /// Initialize internal variant to contain nothing.
-    Content _content{Content::NIL};
-    /// Compute the required raw storage.
-    static constexpr size_t N = std::max<size_t>(sizeof(std::string_view), sizeof(UnknownValueHandler));
-    /// Provide raw storage for the variant.
-    char _store[N];
+    std::string_view
+    operator()(UnknownValueHandler const &handler) const
+    {
+      return handler(_value);
+    }
   };
 
-  struct ValueDefault {
-    using self_type = ValueDefault;
-
-    ValueDefault() = default;
-    ~ValueDefault();
+  /// Visitor functor for handling @c ValueDefault.
+  struct ValueDefaultVisitor {
+    std::string_view _name;
 
-    self_type &operator=(E value);
-    self_type &operator=(const UnknownNameHandler &handler);
-
-    E operator()(std::string_view name) const;
+    E
+    operator()(std::monostate const &) const
+    {
+      throw std::domain_error(detail::what("Lexicon: Unknown name \"{}\"", _name).data());
+    }
 
-    self_type &destroy();
+    E
+    operator()(E const &value) const
+    {
+      return value;
+    }
 
-    Content _content{Content::NIL};
-    static constexpr size_t N = std::max<size_t>(sizeof(E), sizeof(UnknownNameHandler));
-    char _store[N];
+    E
+    operator()(UnknownNameHandler const &handler) const
+    {
+      return handler(_name);
+    }
   };
 
   /// Each unique pair of value and name is stored as an instance of this class.
   /// The primary is stored first and is therefore found by normal lookup.
   struct Item {
-    Item(E, std::string_view);
+    /** Construct with a @a name and a primary @a value.
+     *
+     * @param value The primary value.
+     * @param name The name.
+     *
+     */
+    Item(E value, std::string_view name);
 
     E _value;               ///< Definition value.
     std::string_view _name; ///< Definition name
@@ -417,149 +455,32 @@ Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs)
   return lhs == rhs;
 }
 
-// -------
-
-template <typename E>
-auto
-Lexicon<E>::NameDefault::destroy() -> self_type &
-{
-  if (_content == Content::HANDLER) {
-    reinterpret_cast<UnknownValueHandler *>(_store)->~UnknownValueHandler();
-  }
-  _content = Content::NIL;
-  return *this;
-}
-
-template <typename E> Lexicon<E>::NameDefault::~NameDefault()
-{
-  this->destroy();
-}
-
-template <typename E>
-auto
-Lexicon<E>::NameDefault::operator=(std::string_view name) -> self_type &
-{
-  this->destroy();
-  new (_store) std::string_view(name);
-  _content = Content::SCALAR;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::NameDefault::operator=(const UnknownValueHandler &handler) -> self_type &
-{
-  this->destroy();
-  new (_store) UnknownValueHandler(handler);
-  _content = Content::HANDLER;
-  return *this;
-}
-
-template <typename E>
-std::string_view
-Lexicon<E>::NameDefault::operator()(E value) const
-{
-  switch (_content) {
-  case Content::SCALAR:
-    return *reinterpret_cast<std::string_view const *>(_store);
-    break;
-  case Content::HANDLER:
-    return (*(reinterpret_cast<UnknownValueHandler const *>(_store)))(value);
-    break;
-  default:
-    throw std::domain_error(detail::what("Lexicon: unknown enumeration '{}'", uintmax_t(value)));
-    break;
-  }
-}
-
-// -------
-
-template <typename E>
-auto
-Lexicon<E>::ValueDefault::destroy() -> self_type &
-{
-  if (_content == Content::HANDLER) {
-    reinterpret_cast<UnknownNameHandler *>(_store)->~UnknownNameHandler();
-  }
-  _content = Content::NIL;
-  return *this;
-}
-
-template <typename E> Lexicon<E>::ValueDefault::~ValueDefault()
-{
-  this->destroy();
-}
-
-template <typename E>
-auto
-Lexicon<E>::ValueDefault::operator=(E value) -> self_type &
-{
-  this->destroy();
-  *(reinterpret_cast<E *>(_store)) = value;
-  _content                         = Content::SCALAR;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::ValueDefault::operator=(const UnknownNameHandler &handler) -> self_type &
-{
-  this->destroy();
-  new (_store) UnknownNameHandler(handler);
-  _content = Content::HANDLER;
-  return *this;
-}
-
-template <typename E>
-E
-Lexicon<E>::ValueDefault::operator()(std::string_view name) const
-{
-  switch (_content) {
-  case Content::SCALAR:
-    return *(reinterpret_cast<E const *>(_store));
-    break;
-  case Content::HANDLER:
-    return (*(reinterpret_cast<UnknownNameHandler const *>(_store)))(name);
-    break;
-  default:
-    throw std::domain_error(swoc::LocalBufferWriter<128>().print("Lexicon: unknown name '{}'\0", name).data());
-    break;
-  }
-}
 // -------
 // Lexicon
 
 template <typename E> Lexicon<E>::Lexicon() {}
 
-template <typename E> Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items)
+template <typename E>
+Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items, DefaultHandler handler_1, DefaultHandler handler_2)
 {
   for (auto const &item : items) {
     this->define(item.value, item.names);
   }
-}
 
-template <typename E> Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items)
-{
-  for (auto const &item : items) {
-    this->define(item);
+  for (auto &&h : {handler_1, handler_2}) {
+    this->set_default(h);
   }
 }
 
 template <typename E>
-template <E e>
-Lexicon<E>::Lexicon(const Require<e> &, const std::array<Definition, static_cast<size_t>(e)> &defines)
+Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items, DefaultHandler handler_1, DefaultHandler handler_2)
 {
-  for (auto const &def : defines) {
-    this->define(def);
+  for (auto const &item : items) {
+    this->define(item);
   }
-}
 
-template <typename E>
-template <E e>
-Lexicon<E>::Lexicon(const Require<e> &, const std::array<Pair, static_cast<size_t>(e)> &defines)
-{
-  for (auto const &def : defines) {
-    this->define(def);
+  for (auto &&h : {handler_1, handler_2}) {
+    this->set_default(h);
   }
 }
 
@@ -578,7 +499,7 @@ template <typename E> std::string_view Lexicon<E>::operator[](E value) const
   if (spot != _by_value.end()) {
     return spot->_name;
   }
-  return _name_default(value);
+  return std::visit(NameDefaultVisitor{value}, _name_default);
 }
 
 template <typename E> E Lexicon<E>::operator[](std::string_view const &name) const
@@ -587,7 +508,7 @@ template <typename E> E Lexicon<E>::operator[](std::string_view const &name) con
   if (spot != _by_name.end()) {
     return spot->_value;
   }
-  return _value_default(name);
+  return std::visit(ValueDefaultVisitor{name}, _value_default);
 }
 
 template <typename E>
@@ -636,33 +557,24 @@ Lexicon<E>::define(const Definition &init) -> self_type &
 
 template <typename E>
 auto
-Lexicon<E>::set_default(std::string_view name) -> self_type &
-{
-  _name_default = this->localize(name);
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::set_default(E value) -> self_type &
-{
-  _value_default = value;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::set_default(const UnknownValueHandler &handler) -> self_type &
-{
-  _name_default = handler;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::set_default(const UnknownNameHandler &handler) -> self_type &
+Lexicon<E>::set_default(DefaultHandler const &handler) -> self_type &
 {
-  _value_default = handler;
+  switch (handler.index()) {
+  case 0:
+    break;
+  case 1:
+    _value_default = std::get<1>(handler);
+    break;
+  case 3:
+    _value_default = std::get<3>(handler);
+    break;
+  case 2:
+    _name_default = std::get<2>(handler);
+    break;
+  case 4:
+    _name_default = std::get<4>(handler);
+    break;
+  }
   return *this;
 }
 
diff --git a/unit_tests/test_Lexicon.cc b/unit_tests/test_Lexicon.cc
index cbc0994..db4bdde 100644
--- a/unit_tests/test_Lexicon.cc
+++ b/unit_tests/test_Lexicon.cc
@@ -66,14 +66,13 @@ TEST_CASE("Lexicon Example", "[libts][Lexicon]")
   REQUIRE(exnames[static_cast<Example>(0xBADD00D)] == "INVALID");
   REQUIRE(exnames[exnames[static_cast<Example>(0xBADD00D)]] == Example::INVALID);
 
-  // Case of an enumeration with a "LAST_VALUE".
-  enum class Radio { INVALID, ALPHA, BRAVO, CHARLIE, DELTA, LAST_VALUE };
+  enum class Radio { INVALID, ALPHA, BRAVO, CHARLIE, DELTA };
   using Lex = swoc::Lexicon<Radio>;
-  Lex lex(Lex::Require<Radio::LAST_VALUE>(), {{{Radio::INVALID, {"Invalid"}},
-                                               {Radio::ALPHA, {"Alpha"}},
-                                               {Radio::BRAVO, {"Bravo", "Beta"}},
-                                               {Radio::CHARLIE, {"Charlie"}},
-                                               {Radio::DELTA, {"Delta"}}}});
+  Lex lex({{Radio::INVALID, {"Invalid"}},
+           {Radio::ALPHA, {"Alpha"}},
+           {Radio::BRAVO, {"Bravo", "Beta"}},
+           {Radio::CHARLIE, {"Charlie"}},
+           {Radio::DELTA, {"Delta"}}});
 
   // test structured binding for iteration.
   for (auto const &[key, name] : lex) {
@@ -87,6 +86,7 @@ enum Values { NoValue, LowValue, HighValue, Priceless };
 enum Hex { A, B, C, D, E, F, INVALID };
 
 using ValueLexicon = swoc::Lexicon<Values>;
+using HexLexicon   = swoc::Lexicon<Hex>;
 
 TEST_CASE("Lexicon Constructor", "[libts][Lexicon]")
 {
@@ -162,3 +162,26 @@ TEST_CASE("Lexicon Constructor", "[libts][Lexicon]")
   }
   REQUIRE(mark.all());
 };
+
+TEST_CASE("Lexicon Constructor 2", "[libts][Lexicon]")
+{
+  // Check the various construction cases
+  // No defaults, value default, name default, both, both the other way.
+  const HexLexicon v1({{A, {"A", "ten"}}, {B, {"B", "eleven"}}});
+
+  const HexLexicon v2({{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, INVALID);
+
+  const HexLexicon v3({{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, "Invalid");
+
+  const HexLexicon v4({{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, "Invalid", INVALID);
+
+  const HexLexicon v5{{{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, INVALID, "Invalid"};
+
+  REQUIRE(v1["a"] == A);
+  REQUIRE(v2["q"] == INVALID);
+  REQUIRE(v3[C] == "Invalid");
+  REQUIRE(v4["q"] == INVALID);
+  REQUIRE(v4[C] == "Invalid");
+  REQUIRE(v5["q"] == INVALID);
+  REQUIRE(v5[C] == "Invalid");
+}


(trafficserver-libswoc) 04/08: Add FixedArena.

Posted by bn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d6fbf554738960766c0861baf2da962abb2ab80d
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Sun Nov 10 15:14:22 2019 -0600

    Add FixedArena.
---
 swoc++/include/swoc/MemArena.h | 128 ++++++++++++++++++++++++++++-------------
 unit_tests/test_MemArena.cc    |  31 ++++++++++
 2 files changed, 118 insertions(+), 41 deletions(-)

diff --git a/swoc++/include/swoc/MemArena.h b/swoc++/include/swoc/MemArena.h
index 7e35a1f..2b2e3b0 100644
--- a/swoc++/include/swoc/MemArena.h
+++ b/swoc++/include/swoc/MemArena.h
@@ -22,6 +22,7 @@
 #include <mutex>
 #include <memory>
 #include <utility>
+#include <new>
 
 #include "swoc/MemSpan.h"
 #include "swoc/Scalar.h"
@@ -329,55 +330,89 @@ protected:
   // marks the last block to check. This keeps the set of blocks to check short.
 };
 
+/** Arena of a specific type on top of a @c MemArena.
+ *
+ * @tparam T Type in the arena.
+ *
+ * A pool of unused / free instances of @a T is kept for reuse. If none are available then a new
+ * instance is allocated from the arena.
+ */
+template <typename T> class FixedArena
+{
+  using self_type = FixedArena; ///< Self reference type.
+protected:
+  /// Rebinding type for instances on the free list.
+  struct Item {
+    Item *_next; ///< Next item in the free list.
+  };
+
+  Item _list { nullptr }; ///< List of dead instances.
+  MemArena &_arena; ///< Memory source.
+
+public:
+  /** Construct a pool.
+   *
+   * @param arena The arena for memory.
+   */
+  explicit FixedArena(MemArena &arena);
+
+  /** Create a new instance.
+   *
+   * @tparam Args Constructor argument types.
+   * @param args Constructor arguments.
+   * @return A new instance of @a T.
+   */
+  template <typename... Args> T *make(Args... args);
+
+  /** Destroy an instance.
+   *
+   * @param t The instance to destroy.
+   *
+   * The instance is destructed and then put on the free list for re-use.
+   */
+  void destroy(T *t);
+};
 // Implementation
 
-inline auto
-MemArena::Block::Linkage::next_ptr(Block *b) -> Block *&
+inline auto MemArena::Block::Linkage::next_ptr(Block *b) -> Block *&
 {
   return b->_link._next;
 }
 
-inline auto
-MemArena::Block::Linkage::prev_ptr(Block *b) -> Block *&
+inline auto MemArena::Block::Linkage::prev_ptr(Block *b) -> Block *&
 {
   return b->_link._prev;
 }
 
 inline MemArena::Block::Block(size_t n) : size(n) {}
 
-inline char *
-MemArena::Block::data()
+inline char *MemArena::Block::data()
 {
   return reinterpret_cast<char *>(this + 1);
 }
 
-inline const char *
-MemArena::Block::data() const
+inline const char *MemArena::Block::data() const
 {
   return reinterpret_cast<const char *>(this + 1);
 }
 
-inline bool
-MemArena::Block::contains(const void *ptr) const
+inline bool MemArena::Block::contains(const void *ptr) const
 {
   const char *base = this->data();
   return base <= ptr && ptr < base + size;
 }
 
-inline size_t
-MemArena::Block::remaining() const
+inline size_t MemArena::Block::remaining() const
 {
   return size - allocated;
 }
 
-inline bool
-MemArena::Block::is_full() const
+inline bool MemArena::Block::is_full() const
 {
   return this->remaining() < MIN_FREE_SPACE;
 }
 
-inline MemSpan<void>
-MemArena::Block::alloc(size_t n)
+inline MemSpan<void> MemArena::Block::alloc(size_t n)
 {
   if (n > this->remaining()) {
     throw(std::invalid_argument{"MemArena::Block::alloc size is more than remaining."});
@@ -387,80 +422,91 @@ MemArena::Block::alloc(size_t n)
   return zret;
 }
 
-template <typename T, typename... Args>
-T *
-MemArena::make(Args &&... args)
+template <typename T, typename... Args> T *MemArena::make(Args &&... args)
 {
   return new (this->alloc(sizeof(T)).data()) T(std::forward<Args>(args)...);
 }
 
 inline MemArena::MemArena(size_t n) : _reserve_hint(n) {}
 
-inline MemSpan<void>
-MemArena::Block::remnant()
+inline MemSpan<void> MemArena::Block::remnant()
 {
   return {this->data() + allocated, this->remaining()};
 }
 
-inline MemArena::Block &
-MemArena::Block::discard()
+inline MemArena::Block &MemArena::Block::discard()
 {
   allocated = 0;
   return *this;
 }
 
-inline size_t
-MemArena::size() const
+inline size_t MemArena::size() const
 {
   return _active_allocated;
 }
 
-inline size_t
-MemArena::allocated_size() const
+inline size_t MemArena::allocated_size() const
 {
   return _frozen_allocated + _active_allocated;
 }
 
-inline size_t
-MemArena::remaining() const
+inline size_t MemArena::remaining() const
 {
   return _active.empty() ? 0 : _active.head()->remaining();
 }
 
-inline MemSpan<void>
-MemArena::remnant()
+inline MemSpan<void> MemArena::remnant()
 {
   return _active.empty() ? MemSpan<void>() : _active.head()->remnant();
 }
 
-inline size_t
-MemArena::reserved_size() const
+inline size_t MemArena::reserved_size() const
 {
   return _active_reserved + _frozen_reserved;
 }
 
-inline auto
-MemArena::begin() const -> const_iterator
+inline auto MemArena::begin() const -> const_iterator
 {
   return _active.begin();
 }
 
-inline auto
-MemArena::end() const -> const_iterator
+inline auto MemArena::end() const -> const_iterator
 {
   return _active.end();
 }
 
-inline auto
-MemArena::frozen_begin() const -> const_iterator
+inline auto MemArena::frozen_begin() const -> const_iterator
 {
   return _frozen.begin();
 }
 
-inline auto
-MemArena::frozen_end() const -> const_iterator
+inline auto MemArena::frozen_end() const -> const_iterator
 {
   return _frozen.end();
 }
 
+template <typename T> FixedArena<T>::FixedArena(MemArena &arena) : _arena(arena) {
+  static_assert(sizeof(T) >= sizeof(T*));
+}
+
+template <typename T> template <typename... Args> T *FixedArena<T>::make(Args... args)
+{
+  if (_list._next) {
+    void* t = _list._next;
+    _list._next = _list._next->_next;
+    return new (t) T(std::forward(args)...);
+  }
+  return _arena.template make<T>(std::forward(args)...);
+}
+
+template <typename T> void FixedArena<T>::destroy(T *t)
+{
+  if (t) {
+    t->~T(); // destructor.
+    auto item   = reinterpret_cast<Item *>(t);
+    item->_next = _list._next;
+    _list._next = item;
+  }
+}
+
 } // namespace swoc
diff --git a/unit_tests/test_MemArena.cc b/unit_tests/test_MemArena.cc
index 9a76fc7..a3d6ace 100644
--- a/unit_tests/test_MemArena.cc
+++ b/unit_tests/test_MemArena.cc
@@ -26,6 +26,7 @@
 
 using swoc::MemSpan;
 using swoc::MemArena;
+using swoc::FixedArena;
 using std::string_view;
 using swoc::TextView;
 using namespace std::literals;
@@ -358,3 +359,33 @@ TEST_CASE("MemArena temporary", "[libswoc][MemArena][tmp]")
   }
   REQUIRE(arena.reserved_size() == rsize);
 }
+
+TEST_CASE("FixedArena", "[libswoc][FixedArena]") {
+  struct Thing {
+    int x = 0;
+    std::string name;
+  };
+  MemArena arena;
+  FixedArena<Thing> fa{arena};
+
+  Thing * one = fa.make();
+  Thing * two = fa.make();
+  two->x = 17;
+  two->name = "Bob";
+  fa.destroy(two);
+  Thing * three = fa.make();
+  REQUIRE(three == two); // reused instance.
+  REQUIRE(three->x == 0); // but reconstructed.
+  REQUIRE(three->name.empty() == true);
+  fa.destroy(three);
+  std::array<Thing*, 17> things;
+  for ( auto & ptr : things ) {
+    ptr = fa.make();
+  }
+  two = things[things.size() - 1];
+  for ( auto & ptr : things) {
+    fa.destroy(ptr);
+  }
+  three = fa.make();
+  REQUIRE(two == three);
+};