Merge master into 10-Dev

Conflicts:
      plugins/lua/ts_lua_http_config.c
      tests/gold_tests/autest-site/trafficserver.test.ext
diff --git a/.gitignore b/.gitignore
index 93591ec..4632993 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,6 +67,7 @@
 src/traffic_cop/traffic_cop
 src/traffic_crashlog/traffic_crashlog
 src/traffic_ctl/traffic_ctl
+src/traffic_ctl_jsonrpc/traffic_ctl
 src/traffic_layout/traffic_layout
 src/traffic_logcat/traffic_logcat
 src/traffic_logstats/traffic_logstats
@@ -155,6 +156,7 @@
 
 plugins/experimental/uri_signing/test_uri_signing
 
+mgmt/rpc/overridable_txn_vars.cc
 mgmt/api/traffic_api_cli_remote
 mgmt/tools/traffic_mcast_snoop
 mgmt/tools/traffic_net_config
@@ -204,6 +206,7 @@
 BUILDS
 DEBUG
 RELEASE
+cmake-build-*
 
 # lcov generated files.
 *.gcda
diff --git a/.vscode/launch.json b/.vscode/launch.json
index bebac72..8117283 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -19,25 +19,6 @@
                     "ignoreFailures": true
                 }
             ]
-        },
-        {
-            "name": "(gdb) traffic_manager",
-            "type": "cppdbg",
-            "request": "launch",
-            "program": "${workspaceFolder}/${env:ATS_VSCODE_BUILDDIR}/src/traffic_manager/.libs/traffic_manager",
-            "args": [],
-            "stopAtEntry": false,
-            "cwd": "${workspaceFolder}",
-            "environment": [],
-            "externalConsole": false,
-            "MIMode": "gdb",
-            "setupCommands": [
-                {
-                    "description": "Enable pretty-printing for gdb",
-                    "text": "-enable-pretty-printing",
-                    "ignoreFailures": true
-                }
-            ]
         }
     ]
 }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a18f54d..8402a7a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@
         include
         tests/include
         lib
+        lib/swoc/include
         lib/yamlcpp/include
         proxy
         proxy/hdrs
@@ -72,7 +73,6 @@
 CC_EXEC(traffic_layout src/traffic_layout)
 CC_EXEC(traffic_logcat src/traffic_logcat)
 CC_EXEC(traffic_logstats src/traffic_logstats)
-CC_EXEC(traffic_manager src/traffic_manager)
 CC_EXEC(traffic_server src/traffic_server)
 target_sources(traffic_server PRIVATE src/shared/overridable_txn_vars.cc)
 CC_EXEC(traffic_top src/traffic_top)
diff --git a/README.md b/README.md
index 41742a9..8ee27b4 100644
--- a/README.md
+++ b/README.md
@@ -42,10 +42,10 @@
 │   │   └── quic .......... QUIC implementation
 │   └── utils ............. Utilities
 ├── lib ...................
-│   ├── perl .............. Perl libraries for e.g. mgmt access and configurations
+│   ├── perl .............. Perl libraries for configurations
 │   ├── records ........... Library for config files
 │   └── yamlcpp ........... Library for YAML of C++
-├── mgmt .................. Management server and tools
+├── mgmt .................. JSONRPC server/management and tools
 ├── plugins ............... Stable core plugins
 │   └── experimental ...... Experimental core plugins
 ├── proxy ................. HTTP proxy logic
@@ -63,7 +63,6 @@
 │   ├── traffic_layout .... Display information on the build and runtime directory structure
 │   ├── traffic_logcat .... Convert binary log file to plain text
 │   ├── traffic_logstats .. Log parsing and metrics calculation utility
-│   ├── traffic_manager ... The manager process for Traffic Server
 │   ├── traffic_server .... Main proxy server
 │   ├── traffic_top ....... Top like tool for viewing Traffic Server statistics
 │   ├── traffic_via ....... Tool for decoding the Traffic Server Via header codes
@@ -90,8 +89,7 @@
 
   <https://cwiki.apache.org/confluence/display/TS/Building>.
 
-  As of ATS v7.0.0 and later, gcc 4.8.1 or later is required, since we now use
-  and require the C++11 standard.
+  As of ATS v9.0.0 and later, gcc 7 or later is required, since we now use and require the C++17 standard.
 
 ### Fedora / CentOS / RHEL:
 ```
diff --git a/build/libswoc.m4 b/build/libswoc.m4
index 0858ec8..65c1d61 100644
--- a/build/libswoc.m4
+++ b/build/libswoc.m4
@@ -55,7 +55,7 @@
   saved_ldflags=$LDFLAGS
   saved_cppflags=$CPPFLAGS
 
-  SWOC_LIBS=-lswoc
+  SWOC_LIBS=-ltsswoc
   if test "$libswoc_base_dir" != "/usr"; then
     SWOC_INCLUDES=-I${swoc_include}
     SWOC_LDFLAGS=-L${swoc_ldflags}
@@ -75,7 +75,7 @@
 [
   has_libswoc=no
   SWOC_INCLUDES=-I\${abs_top_srcdir}/lib/swoc/include
-  SWOC_LIBS=-lswoc
+  SWOC_LIBS=-ltsswoc
   SWOC_LDFLAGS=-L\${abs_top_builddir}/lib/swoc
 ])
 
diff --git a/build/plugins.mk b/build/plugins.mk
index a0f0d32..1036a03 100644
--- a/build/plugins.mk
+++ b/build/plugins.mk
@@ -26,7 +26,8 @@
 
 TS_PLUGIN_CPPFLAGS = \
   -I$(abs_top_srcdir)/include \
-  -I$(abs_top_srcdir)/lib
+  -I$(abs_top_srcdir)/lib \
+  @SWOC_INCLUDES@
 
 # Provide a default AM_CPPFLAGS. Automake handles this correctly, but libtool
 # throws an error if we try to do the same with AM_LDFLAGS. Hence, we provide
diff --git a/build/quiche.m4 b/build/quiche.m4
new file mode 100644
index 0000000..3a9c733
--- /dev/null
+++ b/build/quiche.m4
@@ -0,0 +1,88 @@
+dnl -------------------------------------------------------- -*- autoconf -*-
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements.  See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License.  You may obtain a copy of the License at
+dnl
+dnl     http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl
+dnl quiche.m4: Trafficserver's quiche autoconf macros
+dnl
+
+dnl
+dnl TS_CHECK_QUICHE: look for quiche libraries and headers
+dnl
+AC_DEFUN([TS_CHECK_QUICHE], [
+has_quiche=0
+AC_ARG_WITH(quiche, [AS_HELP_STRING([--with-quiche=DIR],[use a specific quiche library])],
+[
+  if test "x$withval" != "xyes" && test "x$withval" != "x"; then
+    quiche_base_dir="$withval"
+    if test "$withval" != "no"; then
+      has_quiche=1
+      case "$withval" in
+      *":"*)
+        quiche_include="`echo $withval | sed -e 's/:.*$//'`"
+        quiche_ldflags="`echo $withval | sed -e 's/^.*://'`"
+        AC_MSG_CHECKING(checking for quiche includes in $quiche_include libs in $quiche_ldflags )
+        ;;
+      *)
+        quiche_include="$withval/include"
+        quiche_ldflags="$withval/lib"
+        AC_MSG_CHECKING(checking for quiche includes in $withval)
+        ;;
+      esac
+    fi
+  fi
+
+  if test -d $quiche_include && test -d $quiche_ldflags && test -f $quiche_include/quiche.h; then
+    AC_MSG_RESULT([ok])
+  else
+    AC_MSG_RESULT([not found])
+  fi
+
+if test "$has_quiche" != "0"; then
+  saved_ldflags=$LDFLAGS
+  saved_cppflags=$CPPFLAGS
+  saved_libs=$LIBS
+  quiche_have_headers=0
+  quiche_have_libs=0
+  if test "$quiche_base_dir" != "/usr"; then
+    TS_ADDTO(CPPFLAGS, [-I${quiche_include}])
+    TS_ADDTO(LDFLAGS, [-L${quiche_ldflags}])
+    TS_ADDTO(LIBS, [-lquiche])
+    TS_ADDTO_RPATH(${quiche_ldflags})
+  fi
+
+  AC_CHECK_LIB([quiche], quiche_connect, [quiche_have_libs=1])
+  if test "$quiche_have_libs" != "0"; then
+    AC_CHECK_HEADERS(quiche.h, [quiche_have_headers=1])
+  fi
+  if test "$quiche_have_headers" != "0"; then
+    AC_SUBST([QUICHE_LIB], [-lquiche])
+    AC_SUBST([QUICHE_CFLAGS], [-I${quiche_include}])
+    AC_CHECK_FUNCS([quiche_config_set_active_connection_id_limit])
+  else
+    has_quiche=0
+    CPPFLAGS=$saved_cppflags
+    LDFLAGS=$saved_ldflags
+    LIBS=$saved_libs
+  fi
+fi
+],
+[
+AC_CHECK_HEADER([quiche.h], [], [has_quiche=0])
+AC_CHECK_LIB([quiche], quiche_connect, [:], [has_quiche=0])
+])
+
+AM_CONDITIONAL([USE_QUICHE], [test $has_quiche -eq 1])
+])
diff --git a/build/yaml-cpp.m4 b/build/yaml-cpp.m4
index e11dc28..2f618f4 100644
--- a/build/yaml-cpp.m4
+++ b/build/yaml-cpp.m4
@@ -82,6 +82,7 @@
 AC_SUBST([YAMLCPP_INCLUDES])
 AC_SUBST([YAMLCPP_LIBS])
 AC_SUBST([YAMLCPP_LDFLAGS])
+AC_DEFINE([YAMLCPP_LIB_VERSION], ["0.7.0"], [yamlcpp library version])
 
 ])
 
diff --git a/ci/jenkins/ats_conf.pl b/ci/jenkins/ats_conf.pl
index 38c7309..2d5599b 100755
--- a/ci/jenkins/ats_conf.pl
+++ b/ci/jenkins/ats_conf.pl
@@ -27,7 +27,6 @@
 
 $recedit->append(line => "");
 $recedit->append(line => "# My local stuff");
-$recedit->append(line => "CONFIG proxy.config.proxy_binary_opts STRING -M --disable_freelist");
 #$recedit->append(line => "CONFIG proxy.config.crash_log_helper STRING /home/admin/bin/invoker_wrap.sh");
 
 # Port setup
diff --git a/configs/Makefile.am b/configs/Makefile.am
index 409dbc0..95499b5 100644
--- a/configs/Makefile.am
+++ b/configs/Makefile.am
@@ -39,7 +39,8 @@
 	splitdns.config.default \
 	ssl_multicert.config.default \
 	strategies.yaml.default \
-	volume.config.default
+	volume.config.default \
+	jsonrpc.yaml.default
 
 install-exec-hook:
 	for dfltcfgfile in $(dist_sysconf_DATA) $(nodist_sysconf_DATA) ; \
diff --git a/configs/jsonrpc.yaml.default b/configs/jsonrpc.yaml.default
new file mode 100644
index 0000000..82665a2
--- /dev/null
+++ b/configs/jsonrpc.yaml.default
@@ -0,0 +1,14 @@
+# jsonrpc.yaml
+#
+# Documentation:
+#   https://docs.trafficserver.apache.org/en/latest/admin-guide/files/jsonrpc.yaml.en.html
+#
+# This configuration file can be used to configure some specific values of
+# the jsonrpc endpoint.
+#
+# Example:
+
+# rpc:
+#   enabled: true
+#   unix:
+#      restricted_api: true
diff --git a/configs/logging.yaml.default b/configs/logging.yaml.default
index a29e653..0e8aebb 100644
--- a/configs/logging.yaml.default
+++ b/configs/logging.yaml.default
@@ -19,16 +19,16 @@
     # reporting tools, simply create a log that uses this format.
     - name: welf
       format: |-
-          id=firewall time="%<cqtd> %<cqtt>" fw=%<phn> pri=6 proto=%<cqus> duration=%<ttmsf> sent=%<psql> rcvd=%<cqhl> src=%<chi> dst=%<shi> dstname=%<shn> user=%<caun> op=%<cqhm> arg="%<cqup>" result=%<pssc> ref="%<{Referer}cqh>" agent="%<{user-agent}cqh>" cache=%<crc>
+          id=firewall time="%<cqtd> %<cqtt>" fw=%<phn> pri=6 proto=%<pqus> duration=%<ttmsf> sent=%<psql> rcvd=%<cqhl> src=%<chi> dst=%<shi> dstname=%<shn> user=%<caun> op=%<cqhm> arg="%<pqup>" result=%<pssc> ref="%<{Referer}cqh>" agent="%<{user-agent}cqh>" cache=%<crc>
     # Squid Log Format with seconds resolution timestamp.
     # The following is the squid format but with a seconds-only timestamp
     # (cqts) instead of a seconds and milliseconds timestamp (cqtq).
     - name: squid_seconds_only_timestamp
-      format: '%<cqts> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>'
+      format: '%<cqts> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<pquc> %<caun> %<phr>/%<shn> %<psct>'
 
     # Squid Log Format.
     - name: squid
-      format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>'
+      format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<pquc> %<caun> %<phr>/%<shn> %<psct>'
 
     # Common Log Format.
     - name: common
diff --git a/configs/records.config.default.in b/configs/records.config.default.in
index 2f2b617..dc22bf7 100644
--- a/configs/records.config.default.in
+++ b/configs/records.config.default.in
@@ -35,7 +35,6 @@
 #    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/parent.config.en.html
 ##############################################################################
 CONFIG proxy.config.http.parent_proxy.retry_time INT 300
-CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout INT 30
 CONFIG proxy.config.http.forward.proxy_auth_to_parent INT 0
 CONFIG proxy.config.http.uncacheable_requests_bypass_parent INT 1
 
@@ -60,7 +59,6 @@
 CONFIG proxy.config.http.connect_attempts_max_retries_dead_server INT 1
 CONFIG proxy.config.http.connect_attempts_rr_retries INT 3
 CONFIG proxy.config.http.connect_attempts_timeout INT 30
-CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800
 CONFIG proxy.config.http.down_server.cache_time INT 60
 
 ##############################################################################
diff --git a/configure.ac b/configure.ac
index 066e809..cdca9a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -547,14 +547,13 @@
 
 AC_MSG_CHECKING([whether to install example plugins])
 AC_ARG_ENABLE([example-plugins],
-  [AS_HELP_STRING([--enable-example-plugins],[build and install example plugins])],
+  [AS_HELP_STRING([--enable-example-plugins],[Build and install example plugins])],
   [],
   [enable_example_plugins=no]
 )
 AC_MSG_RESULT([$enable_example_plugins])
 AM_CONDITIONAL([BUILD_EXAMPLE_PLUGINS], [ test "x${enable_example_plugins}" = "xyes" ])
 
-
 #
 # Test tools. The test tools are always built, but not always installed. Installing
 # them is useful for QA, but not useful for most users, so we default this to disabled.
@@ -1402,6 +1401,7 @@
 AC_CHECK_FUNCS([port_create strlcpy strlcat sysconf sysctlbyname getpagesize])
 AC_CHECK_FUNCS([getreuid getresuid getresgid setreuid setresuid getpeereid getpeerucred])
 AC_CHECK_FUNCS([strsignal psignal psiginfo accept4])
+AC_CHECK_FUNCS([sendmmsg])
 
 # Check for eventfd() and sys/eventfd.h (both must exist ...)
 AC_CHECK_HEADERS([sys/eventfd.h], [
@@ -1437,6 +1437,16 @@
 # Check for optional luajit library
 TS_CHECK_LUAJIT
 
+# Check for optional quiche library
+TS_CHECK_QUICHE
+if test "${has_quiche}" = "1"; then
+enable_quic=yes
+## Doing these again for Quiche
+AM_CONDITIONAL([ENABLE_QUIC], [test "x$enable_quic" = "xyes"])
+TS_ARG_ENABLE_VAR([use], [quic])
+AC_SUBST(use_quic)
+fi
+
 # Check for optional WAVM library
 TS_CHECK_WAVM
 
@@ -1887,11 +1897,6 @@
 # We should only build traffic_top if we have curses
 AM_CONDITIONAL([BUILD_TRAFFIC_TOP], [test "x$ax_cv_curses" = "xyes"])
 
-AC_CHECK_HEADERS([mysql/mysql.h], [has_mysql=1],[has_mysql=0])
-AC_CHECK_LIB([mysqlclient],[mysql_info],[AC_SUBST([LIB_MYSQLCLIENT],["-lmysqlclient"])],[has_mysql=0])
-AC_SUBST(has_mysql)
-AM_CONDITIONAL([HAS_MYSQL], [ test "x${has_mysql}" = "x1" ])
-
 AC_CHECK_HEADERS([kclangc.h], [
   AC_CHECK_LIB([kyotocabinet], [kcdbopen], [
     AC_SUBST([LIB_KYOTOCABINET], ["-lkyotocabinet"])
@@ -2377,6 +2382,8 @@
   mgmt/api/Makefile
   mgmt/api/include/Makefile
   mgmt/utils/Makefile
+  mgmt/rpc/Makefile
+  mgmt/config/Makefile
   plugins/Makefile
   proxy/Makefile
   proxy/hdrs/Makefile
diff --git a/contrib/install_trafficserver.sh b/contrib/install_trafficserver.sh
index 528ccf8..8da53cd 100644
--- a/contrib/install_trafficserver.sh
+++ b/contrib/install_trafficserver.sh
@@ -66,7 +66,6 @@
 
 function killAll() {
     killall traffic_cop
-    killall traffic_manager
     killall traffic_server
 }
 
diff --git a/doc/.tx/config b/doc/.tx/config
index 317fba5..c7baab4 100644
--- a/doc/.tx/config
+++ b/doc/.tx/config
@@ -427,11 +427,6 @@
 source_file = _build/locale/pot/admin-guide/plugins/mp4.en.pot
 source_lang = en
 
-[apache-traffic-server-6x.admin-guide--plugins--mysql_remap_en]
-file_filter = locale/<lang>/LC_MESSAGES/admin-guide/plugins/mysql_remap.en.po
-source_file = _build/locale/pot/admin-guide/plugins/mysql_remap.en.pot
-source_lang = en
-
 [apache-traffic-server-6x.admin-guide--plugins--regex_remap_en]
 file_filter = locale/<lang>/LC_MESSAGES/admin-guide/plugins/regex_remap.en.po
 source_file = _build/locale/pot/admin-guide/plugins/regex_remap.en.pot
@@ -697,9 +692,9 @@
 source_file = _build/locale/pot/developer-guide/api/functions/TSContMutexGet.en.pot
 source_lang = en
 
-[apache-traffic-server-6x.developer-guide--api--functions--TSContSchedule_en]
-file_filter = locale/<lang>/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po
-source_file = _build/locale/pot/developer-guide/api/functions/TSContSchedule.en.pot
+[apache-traffic-server-6x.developer-guide--api--functions--TSContScheduleOnPool_en]
+file_filter = locale/<lang>/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po
+source_file = _build/locale/pot/developer-guide/api/functions/TSContScheduleOnPool.en.pot
 source_lang = en
 
 [apache-traffic-server-6x.developer-guide--api--functions--TSDebug_en]
diff --git a/doc/admin-guide/files/index.en.rst b/doc/admin-guide/files/index.en.rst
index 9925ec2..4bf2aca 100644
--- a/doc/admin-guide/files/index.en.rst
+++ b/doc/admin-guide/files/index.en.rst
@@ -39,6 +39,7 @@
    storage.config.en
    strategies.yaml.en
    volume.config.en
+   jsonrpc.yaml.en
 
 :doc:`cache.config.en`
    Defines if, how, and for what durations |TS| caches objects, based on
@@ -87,6 +88,9 @@
 :doc:`volume.config.en`
     Defines cache space usage by individual protocols.
 
+:doc:`jsonrpc.yaml.en`
+    Defines some of the configurable arguments of the jsonrpc endpoint.
+
 .. note::
 
    Currently the YAML parsing library has a bug where line number counting
diff --git a/doc/admin-guide/files/jsonrpc.yaml.en.rst b/doc/admin-guide/files/jsonrpc.yaml.en.rst
new file mode 100644
index 0000000..86fb8f3
--- /dev/null
+++ b/doc/admin-guide/files/jsonrpc.yaml.en.rst
@@ -0,0 +1,124 @@
+
+.. 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
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. |RPC| replace:: JSONRPC 2.0
+.. configfile:: jsonrpc.yaml
+
+.. _admin-jsonrpc:
+
+
+jsonrpc.yaml
+************
+
+.. _admin-jsonrpc-description:
+
+The :file:`jsonrpc.yaml` file defines some of the configurable arguments of the
+jsonrpc endpoint.
+
+Description
+===========
+
+|TS| Implements and exposes management calls using a JSONRPC API.  This API is
+base on the following two things:
+
+* `JSON  <https://www.json.org/json-en.html>`_  format. Lightweight
+  data-interchange format. It is easy for humans to read and write.
+  It is easy for machines to parse and generate. It's basically a  collection of
+  name/value pairs.
+
+* `JSONRPC 2.0 <https://www.jsonrpc.org/specification>`_ protocol. Stateless,
+  light-weight remote procedure call (RPC) protocol.
+  Primarily this specification defines several data structures and the rules
+  around their processing.
+
+
+In order for programs to communicate with |TS|, the server exposes a
+``JSONRRPC 2.0`` API where programs can communicate with it. In this document you
+will find some of the configurable arguments that can be changed.
+
+
+.. _admin-jsonrpc-configuration:
+
+Configuration
+=============
+
+.. note::
+
+   |TS| will start the |RPC| server without any need for configuration.
+
+If a non-default configuration is needed, the following describes the configuration
+structure.
+
+File `jsonrpc.yaml` is a YAML format. The default configuration looks like:
+
+.. code-block:: yaml
+
+   rpc:
+     enabled: true
+     unix:
+       restricted_api: true
+
+
+===================== ==========================================================
+Field Name            Description
+===================== ==========================================================
+``enabled``           Enable/disable toggle for the whole implementation, server
+                      will not start if this is false/no
+``unix``              Specific definitions as per transport.
+===================== ==========================================================
+
+
+IPC Socket (``unix``)
+---------------------
+
+Unix Domain Socket related configuration.
+
+===================================== =========================================================================================
+Field Name                            Description
+===================================== =========================================================================================
+``lock_path_name``                    Lock path, including the file name. (changing this may have impacts in :program:`traffic_ctl`)
+``sock_path_name``                    Sock path, including the file name. This will be used as ``sockaddr_un.sun_path``. (changing
+                                      this may have impacts in :program:`traffic_ctl`)
+``backlog``                           Check https://man7.org/linux/man-pages/man2/listen.2.html
+``max_retry_on_transient_errors``     Number of times the implementation is allowed to retry when a transient error is encountered.
+``restricted_api``                    This setting specifies whether the jsonrpc node access should be restricted to root processes.
+                                      If this is set to ``false``, then on platforms that support passing process credentials, non-root
+                                      processes will be allowed to make read-only JSONRPC calls. Any calls that modify server state
+                                      (eg. setting a configuration variable) will still be restricted to root processes. If set to ``true``
+                                      then only root processes will be allowed to perform any api call.
+                                      If restricted, the Unix Domain Socket will be created with `0700` permissions, otherwise `0777`.
+                                      ``true`` by default.
+                                      In case of an unauthorized call is made, a corresponding rpc error will be returned, you can
+                                      check :ref:`jsonrpc-node-errors-unauthorized-action` for details about the errors.
+===================================== =========================================================================================
+
+
+.. note::
+
+   Currently, there is only 1 communication mechanism supported. Unix Domain Sockets
+
+
+See also
+========
+
+:ref:`traffic_ctl_jsonrpc`
diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index 636b7dc..5f4080f 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -152,7 +152,7 @@
 For example, we could override the `proxy.config.product_company`_ variable
 like this::
 
-   $ PROXY_CONFIG_PRODUCT_COMPANY=example traffic_manager &
+   $ PROXY_CONFIG_PRODUCT_COMPANY=example traffic_server &
    $ traffic_ctl config get proxy.config.product_company
 
 .. _configuration-variables:
@@ -189,25 +189,21 @@
 
 .. ts:cv:: CONFIG proxy.config.proxy_binary STRING traffic_server
 
-   The name of the executable that runs the :program:`traffic_server` process.
+ .. important::
 
-   If you want to set Environment Variables for :program:`traffic_server` process, use a wrapper script like below. ::
-
-     CONFIG proxy.config.proxy_binary STRING start_traffic_server.sh
-
-   ::
-
-     #!/bin/sh
-     export ASAN_OPTIONS=detect_leaks=1
-     /opt/ats/bin/traffic_server "$@"
+      This is now deprecated. traffic_manager is no longer supported.
 
 .. ts:cv:: CONFIG proxy.config.proxy_binary_opts STRING -M
 
-   The :ref:`command-line options <traffic_server>` for starting |TS|.
+   .. important::
+
+      This is now deprecated
 
 .. ts:cv:: CONFIG proxy.config.manager_binary STRING traffic_manager
 
-   The name of the executable that runs the :program:`traffic_manager` process.
+   .. important::
+
+      This is now deprecated. traffic_manager is no longer supported.
 
 .. ts:cv:: CONFIG proxy.config.memory.max_usage INT 0
    :units: bytes
@@ -217,8 +213,9 @@
 
 .. ts:cv:: CONFIG proxy.config.env_prep STRING
 
-   The script executed before the :program:`traffic_manager` process spawns
-   the :program:`traffic_server` process.
+   .. important::
+
+      This is now deprecated. traffic_manager is no longer supported.
 
 .. ts:cv:: CONFIG proxy.config.syslog_facility STRING LOG_DAEMON
 
@@ -231,7 +228,7 @@
 
    This is used for log rolling configuration so |TS| knows the path of the
    output file that should be rolled. This configuration takes the name of the
-   file receiving :program:`traffic_server` and :program:`traffic_manager`
+   file receiving :program:`traffic_server`
    process output that is set via the ``--bind_stdout`` and ``--bind_stderr``
    :ref:`command-line options <traffic_server>`.
    :ts:cv:`proxy.config.output.logfile` is used only to identify the name of
@@ -389,8 +386,9 @@
 .. ts:cv:: CONFIG proxy.config.restart.active_client_threshold INT 0
    :reloadable:
 
-   This setting specifies the number of active client connections
-   for use by :option:`traffic_ctl server restart --drain`.
+   .. important::
+
+      Deprecated. traffic_manager is no longer supported.
 
 .. ts:cv:: CONFIG proxy.config.restart.stop_listening INT 0
    :reloadable:
@@ -568,16 +566,14 @@
 
    When we trigger a throttling scenario, this how long our accept() are delayed.
 
-Local Manager
-=============
+Management
+==========
 
 .. ts:cv:: CONFIG proxy.node.config.manager_log_filename STRING manager.log
 
-   The name of the file to which :program:`traffic_manager` logs will be emitted.
+   .. important::
 
-   If this is set to ``stdout`` or ``stderr``, then all :program:`traffic_manager`
-   logging will go to the stdout or stderr stream, respectively.
-
+      This is now deprecated. traffic_manager is no longer supported.
 
 .. ts:cv:: CONFIG proxy.config.admin.user_id STRING nobody
 
@@ -603,44 +599,8 @@
 
 .. ts:cv:: CONFIG proxy.config.admin.api.restricted INT 0
 
-   This setting specifies whether the management API should be restricted to
-   root processes. If this is set to ``0``, then on platforms that support
-   passing process credentials, non-root processes will be allowed to make
-   read-only management API calls. Any management API calls that modify server
-   state (eg. setting a configuration variable) will still be restricted to
-   root processes.
-
-   This setting is not reloadable, since it is must be applied when
-   :program:`traffic_manager` initializes.
-
-.. ts:cv:: CONFIG proxy.config.track_config_files INT 1
-
-   Enables (``1``) or disables (``0``) tracking configuration file updates.
-   This setting is enabled by default, meaning that configuration files are monitored for changes.
-   Having tracking enabled is a dependency for :option:`traffic_ctl config status` to function. However,
-   tracking the files is implemented via a frequent call to ``stat()`` which may be problematic
-   in some deployments. If the call to ``stat()`` on configuration files causes problems, then
-   it can be avoided by setting this value to ``0`` at the cost of disabling the config status feature
-   for :program:`traffic_ctl`.
-
-   This setting is not reloadable, since it is must be applied when
-   :program:`traffic_manager` initializes.
-
-.. ts:cv:: CONFIG proxy.node.config.manager_exponential_sleep_ceiling INT 60
-
-   In case of :program:`traffic_manager` is unable to start :program:`traffic_server`,
-   this setting specifies the maximum amount of seconds that the :program:`traffic_manager`
-   process should wait until it tries again to restart :program:`traffic_server`.
-   In case of :program:`traffic_manager` failing to start :program:`traffic_server`, it will
-   retry exponentially until it reaches the ceiling time.
-
-.. ts:cv:: CONFIG proxy.node.config.manager_retry_cap INT 5
-
-   This setting specifies the number of times that :program:`traffic_manager` will retry
-   to restart :program:`traffic_server` once the  maximum ceiling time is reached.
-
-.. note::
-   If set to ``0``, no cap will take place.
+   This is now deprecated, please refer to :ref:`admin-jsonrpc-configuration` to find
+   out about the new admin API mechanism.
 
 Alarm Configuration
 ===================
@@ -1333,14 +1293,6 @@
    The total number of connection attempts allowed per parent for a specific
    transaction, if multiple parents are used.
 
-.. ts:cv:: CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout INT 30
-   :reloadable:
-   :overridable:
-
-   The timeout value (in seconds) for parent cache connection attempts.
-
-   See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts.
-
 .. ts:cv:: CONFIG proxy.config.http.parent_proxy.mark_down_hostdb INT 0
    :reloadable:
    :overridable:
@@ -1636,15 +1588,9 @@
 
    Set a limit for the number of concurrent connections to an upstream server group. A value of
    ``0`` disables checking. If a transaction attempts to connect to a group which already has the
-   maximum number of concurrent connections the transaction either rechecks after a delay or a 503
+   maximum number of concurrent connections a 503
    (``HTTP_STATUS_SERVICE_UNAVAILABLE``) error response is sent to the user agent. To configure
 
-   Number of transactions that can be delayed concurrently
-      See :ts:cv:`proxy.config.http.per_server.connection.queue_size`.
-
-   How long to delay before rechecking
-      See :ts:cv:`proxy.config.http.per_server.connection.queue_delay`.
-
    Upstream server group definition
       See :ts:cv:`proxy.config.http.per_server.connection.match`.
 
@@ -1678,26 +1624,6 @@
    This setting is independent of the :ts:cv:`setting for upstream session sharing matching
    <proxy.config.http.server_session_sharing.match>`.
 
-.. ts:cv:: CONFIG proxy.config.http.per_server.connection.queue_size INT 0
-   :reloadable:
-
-   Controls the number of transactions that can be waiting on an upstream server group.
-
-   ``-1``
-      Unlimited.
-
-   ``0``
-      Never wait. If the connection maximum has been reached immediately respond with an error.
-
-   A positive number
-      If there are less than this many waiting transactions, delay this transaction and try again. Otherwise respond immediately with an error.
-
-.. ts:cv:: CONFIG proxy.config.http.per_server.connection.queue_delay INT 100
-   :reloadable:
-   :units: milliseconds
-
-   If a transaction is delayed due to too many connections in an upstream server group, delay this amount of time before checking again.
-
 .. ts:cv:: CONFIG proxy.config.http.per_server.connection.alert_delay INT 60
    :reloadable:
    :units: seconds
@@ -1731,15 +1657,6 @@
 
    See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts.
 
-.. ts:cv:: CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800
-   :reloadable:
-   :overridable:
-
-   The timeout value (in seconds) for an origin server connection when the client request is a ``POST`` or ``PUT``
-   request.
-
-   See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts.
-
 .. ts:cv:: CONFIG proxy.config.http.post.check.content_length.enabled INT 1
 
     Enables (``1``) or disables (``0``) checking the Content-Length: Header for a POST request.
@@ -3910,7 +3827,7 @@
 Client-Related Configuration
 ----------------------------
 
-.. ts:cv:: CONFIG proxy.config.ssl.client.verify.server.policy STRING PERMISSIVE
+.. ts:cv:: CONFIG proxy.config.ssl.client.verify.server.policy STRING ENFORCED
    :reloadable:
    :overridable:
 
@@ -4062,6 +3979,51 @@
 
    Enables (``1``) or disables (``0``) TLSv1_3 in the ATS client context. If not specified, enabled by default
 
+.. ts:cv:: CONFIG proxy.config.ssl.client.alpn_protocols STRING ""
+   :overridable:
+
+   Sets the ALPN string that |TS| will send to the origin in the ClientHello of TLS handshakes.
+   Configuring this to an empty string (the default configuration) means that the ALPN extension
+   will not be sent as a part of the TLS ClientHello.
+
+   Configuring the ALPN string provides a mechanism to control origin-side HTTP protocol
+   negotiation. Configuring this requires an understanding of the ALPN TLS protocol extension. See
+   `RFC 7301 <https://www.rfc-editor.org/rfc/rfc7301.html>`_ for details about the ALPN protocol.
+   See the official `IANA ALPN protocol registration
+   <https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids>`_
+   for the official list of ALPN protocol names. As a summary, the ALPN string is a comma-separated
+   (no spaces) list of protocol names that the TLS client (|TS| in this case) supports. On the TLS
+   server side (origin side in this case), the names are compared in order to the list of protocols
+   supported by the origin. The first match is used, thus the ALPN list should be listed in
+   decreasing order of preference. If no match is found, the TLS server is expected (per the RFC) to
+   fail the TLS handshake with a fatal "no_application_protocol" alert.
+
+   Currently, |TS| supports the following ALPN protocol names:
+
+    - ``http/1.0``
+    - ``http/1.1``
+
+   Here are some example configurations and the consequences of each:
+
+   ================================ ======================================================================
+   Value                            Description
+   ================================ ======================================================================
+   ``""``                           No ALPN extension is sent by |TS| in origin-side TLS handshakes.
+                                    |TS| will assume an HTTP/1.1 connection in this case.
+   ``"http/1.1"``                   Only HTTP/1.1 is advertized by |TS|. Thus, the origin will
+                                    either negotiate HTTP/1.1, or it will fail the handshake if that
+                                    is not supported by the origin.
+   ``"http/1.1,http/1.0"``          Both HTTP/1.1 and HTTP/1.0 are supported by |TS|, but HTTP/1.1
+                                    is preferred.
+   ``"h2,http/1.1,http/1.0"``       HTTP/2 is preferred by |TS| over HTTP/1.1 and HTTP/1.0. Thus, if the
+                                    origin supports HTTP/2, it will be used for the connection. If
+                                    not, it will fall back to HTTP/1.1 or, if that is not supported,
+                                    HTTP/1.0. (HTTP/2 to origin is currently not supported by |TS|.)
+   ``"h2"``                         |TS| only advertizes HTTP/2 support. Thus, the origin will
+                                    either negotiate HTTP/2 or fail the handshake. (HTTP/2 to origin
+                                    is currently not supported by |TS|.)
+   ================================ ======================================================================
+
 .. ts:cv:: CONFIG proxy.config.ssl.async.handshake.enabled INT 0
 
    Enables the use of OpenSSL async job during the TLS handshake.  Traffic
@@ -4202,7 +4164,40 @@
    :reloadable:
    :units: bytes
 
-   The initial window size for inbound connections.
+   The initial HTTP/2 stream window size for inbound connections that |TS| as a
+   receiver advertises to the peer. See IETF RFC 9113 section 5.2 for details
+   concerning HTTP/2 flow control. See
+   :ts:cv:`proxy.config.http2.flow_control.policy_in` for how HTTP/2 stream and
+   session windows are maintained over the lifetime of HTTP/2 sessions.
+
+.. ts:cv:: CONFIG proxy.config.http2.flow_control.policy_in INT 0
+   :reloadable:
+
+   Specifies the mechanism |TS| uses to maintian flow control via the HTTP/2
+   stream and session windows for inbound connections. See IETF RFC 9113
+   section 5.2 for details concerning HTTP/2 flow control.
+
+   ===== ===========================================================================================
+   Value Description
+   ===== ===========================================================================================
+   ``0`` Session and stream receive windows are initialized and maintained at the value as specified
+         in :ts:cv:`proxy.config.http2.initial_window_size_in` over the lifetime of HTTP/2
+         sessions.
+   ``1`` Session receive windows are initialized to the value of the product of
+         :ts:cv:`proxy.config.http2.initial_window_size_in` and
+         :ts:cv:`proxy.config.http2.max_concurrent_streams_in` and are maintained as such over the
+         lifetime of HTTP/2 sessions. Stream windows are initialized to the value of
+         :ts:cv:`proxy.config.http2.initial_window_size_in` and are maintained as such over the
+         lifetime of each HTTP/2 stream.
+   ``2`` Session receive windows are initialized to the value of the product of
+         :ts:cv:`proxy.config.http2.initial_window_size_in` and
+         :ts:cv:`proxy.config.http2.max_concurrent_streams_in` and are maintained as such over the
+         lifetime of HTTP/2 sessions. Stream windows are initialized to the value of
+         :ts:cv:`proxy.config.http2.initial_window_size_in` but are dynamically adjusted to the
+         session window size divided by the number of concurrent streams over the lifetime of HTTP/2
+         sessions. That is, stream window sizes dynamically adjust to fill the session window in
+         a way that shares the window equally among all concurrent streams.
+   ===== ===========================================================================================
 
 .. ts:cv:: CONFIG proxy.config.http2.max_frame_size INT 16384
    :reloadable:
@@ -4601,6 +4596,19 @@
    This is just for debugging. Do not change it from the default value unless
    you really understand what this is.
 
+UDP Configuration
+=====================
+
+.. ts:cv:: CONFIG proxy.config.udp.threads INT 0
+
+   Specifies the number of UDP threads to run. By default 0 threads are dedicated to UDP,
+   which results in effectively disabling UDP support.
+
+.. ts:cv:: CONFIG proxy.config.udp.enable_gso INT 0
+
+   Enables (``1``) or disables (``0``) UDP GSO. When enabled, |TS| tries to use UDP GSO,
+   and disables it automatically if it causes send errors.
+
 Plug-in Configuration
 =====================
 
@@ -4692,7 +4700,7 @@
 
    Enables (1) or disables (0) the SOCKS proxy option. As a SOCKS
    proxy, |TS| receives SOCKS traffic (usually on port
-   1080) and forwards all requests directly to the SOCKS server.
+   1)    and forwards all requests directly to the SOCKS server.
 
 .. ts:cv::  CONFIG proxy.config.socks.accept_port INT 1080
 
diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst
index 5c11420..f5a5868 100644
--- a/doc/admin-guide/files/sni.yaml.en.rst
+++ b/doc/admin-guide/files/sni.yaml.en.rst
@@ -140,9 +140,6 @@
                                     By default this is :ts:cv:`proxy.config.http2.default_buffer_water_mark`.
                                     NOTE: Connection coalescing may prevent this taking effect.
 
-disable_h2                Inbound   Deprecated for the more general h2 setting.  Setting disable_h2
-                                    to :code:`true` is the same as setting http2 to :code:`on`.
-
 tunnel_route              Inbound   Destination as an FQDN and port, separated by a colon ``:``.
                                     Match group number can be specified by ``$N`` where N should refer to a specified group
                                     in the FQDN, ``tunnel_route: $1.domain``.
diff --git a/doc/admin-guide/installation/index.en.rst b/doc/admin-guide/installation/index.en.rst
index fed6f8a..20dce5c 100644
--- a/doc/admin-guide/installation/index.en.rst
+++ b/doc/admin-guide/installation/index.en.rst
@@ -226,7 +226,7 @@
 
 To stop Traffic Server, always use the :program:`trafficserver` command,
 passing in the attribute ``stop``. This command stops all the Traffic
-Server processes (:program:`traffic_manager` and :program:`traffic_server`).
+Server processes (:program:`traffic_server`).
 Do not manually stop processes, as this can lead to unpredictable results. ::
 
     bin/trafficserver stop
diff --git a/doc/admin-guide/introduction.en.rst b/doc/admin-guide/introduction.en.rst
index 25d5c79..c988ba1 100644
--- a/doc/admin-guide/introduction.en.rst
+++ b/doc/admin-guide/introduction.en.rst
@@ -165,28 +165,14 @@
 |TS| Processes
 --------------
 
-|TS| contains two processes that work together to serve
-requests and manage, control, and monitor the health of the system.
+|TS| contains a single processes to serve requests, manage administrative
+calls(JSONRPC) and handle configuration.
 
 #. The :program:`traffic_server` process is the transaction processing engine
    of |TS|. It is responsible for accepting connections,
    processing protocol requests, and serving documents from the cache or
    origin server.
 
-#. The :program:`traffic_manager` process is the command and control facility
-   of the |TS|, responsible for launching, monitoring, and
-   reconfiguring the :program:`traffic_server` process. The :program:`traffic_manager`
-   process is also responsible for the proxy autoconfiguration port, the
-   statistics interface, and virtual IP failover.
-
-   If the :program:`traffic_manager` process detects a :program:`traffic_server`
-   process failure, it instantly restarts the process but also maintains
-   a connection queue of all incoming requests. All incoming connections
-   that arrive in the several seconds before full server restart are
-   saved in the connection queue and processed in first-come,
-   first-served order. This connection queueing shields users from any
-   server restart downtime.
-
 Administration Tools
 --------------------
 
@@ -201,9 +187,9 @@
    changes you make through :program:`traffic_ctl` are
    automatically made to the configuration files as well.
 
--  Finally, there is a clean C API which can be put to good use from a
-   multitude of languages. The |TS| Admin Client demonstrates
-   this for Perl.
+-  Finally, there is a JSONRPC 2.0 interface which provides access to the JSONRPC 2.0
+   Administrative endpoint which allow you to implement your own tool by just using
+   JSON or YAML. Check :ref:`jsonrpc-node` for more information.
 
 Traffic Analysis Options
 ========================
diff --git a/doc/admin-guide/layer-4-routing.en.rst b/doc/admin-guide/layer-4-routing.en.rst
index 2485a43..463d947 100644
--- a/doc/admin-guide/layer-4-routing.en.rst
+++ b/doc/admin-guide/layer-4-routing.en.rst
@@ -62,8 +62,8 @@
 connection is treated as if the user agent had sent a
 ``CONNECT`` to the upstream and forwards the "`CLIENT HELLO
 <https://tools.ietf.org/html/rfc5246#section-7.4.1.2>`__" to it. In addition to the method appearing
-as ``CONNECT``, be aware that logs printing the URL via the ``<%cquc>`` field format will show the
-scheme in the URL as ``tunnel``. The scheme as printed via ``<%cqus>``, however, will show the
+as ``CONNECT``, be aware that logs printing the URL via the ``<%pquc>`` field format will show the
+scheme in the URL as ``tunnel``. The scheme as printed via ``<%pqus>``, however, will show the
 scheme used in the original client request.
 
 Example
diff --git a/doc/admin-guide/logging/examples.en.rst b/doc/admin-guide/logging/examples.en.rst
index 9d20092..1671697 100644
--- a/doc/admin-guide/logging/examples.en.rst
+++ b/doc/admin-guide/logging/examples.en.rst
@@ -212,7 +212,7 @@
 
    formats:
    - name: squid
-     format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>'
+     format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<pquc> %<caun> %<phr>/%<shn> %<psct>'
 
 Hourly Rotated Squid Proxy Logs
 ===============================
@@ -225,7 +225,7 @@
 
    formats:
    - name: squid
-     format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>'
+     format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<pquc> %<caun> %<phr>/%<shn> %<psct>'
 
    logs:
    - mode: ascii
@@ -290,7 +290,7 @@
 
    filters:
    - name: canaryfilter
-     accept: cqup MATCH "/nightmare/scenario/dont/touch"
+     accept: pqup MATCH "/nightmare/scenario/dont/touch"
 
    logs:
    - mode: ascii_pipe
diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst
index e1ba9f3..4e65bc0 100644
--- a/doc/admin-guide/logging/formatting.en.rst
+++ b/doc/admin-guide/logging/formatting.en.rst
@@ -558,7 +558,6 @@
 Protocols and Versions
 ~~~~~~~~~~~~~~~~~~~~~~
 
-.. _cqhv:
 .. _cqpv:
 .. _csshv:
 .. _sshv:
@@ -569,8 +568,6 @@
 ===== ===================== ===================================================
 Field Source                Description
 ===== ===================== ===================================================
-cqhv  Client Request        Client request HTTP version. Deprecated since 9.0.
-                            Use ``cqpv`` instead.
 cqpv  Client Request        Client request protocol and version.
 sqpv  Proxy Request         Origin negotiated protocol and version
 csshv Cached Proxy Response Origin server's HTTP version from cached version of
@@ -808,6 +805,10 @@
 .. _cquc:
 .. _cqup:
 .. _cqus:
+.. _pqu:
+.. _pquc:
+.. _pqup:
+.. _pqus:
 .. _cquuc:
 .. _cquup:
 .. _cquuh:
@@ -821,13 +822,26 @@
 cqu   Proxy Request  URL of the proxy request from |TS| to the origin. If a
                      remap rule is used (:file:`remap.config`), the original
                      client URL is replaced with the URL in the target of the
-                     remap rule.
+                     remap rule. Deprecated since 10.0. Use ``pqu`` instead.
 cquc  Proxy Request  Canonical URL from the proxy request to origin. This field
                      differs from cqu_ by having its contents URL-escaped
                      (spaces and various other characters are replaced by
+                     percent-escaped entity codes). Deprecated since 10.0.
+                     Use ``pquc`` instead.
+cqup  Proxy Request  Path component from the proxy request. Deprecated since
+                     10.0. Use ``pqup`` instead.
+cqus  Proxy Request  URL scheme from the proxy request. Deprecated since 10.0.
+                     Use ``pqus`` instead.
+pqu   Proxy Request  URL of the proxy request from |TS| to the origin. If a
+                     remap rule is used (:file:`remap.config`), the original
+                     client URL is replaced with the URL in the target of the
+                     remap rule.
+pquc  Proxy Request  Canonical URL from the proxy request to origin. This field
+                     differs from pqu_ by having its contents URL-escaped
+                     (spaces and various other characters are replaced by
                      percent-escaped entity codes).
-cqup  Proxy Request  Path component from the proxy request.
-cqus  Proxy Request  URL scheme from the proxy request.
+pqup  Proxy Request  Path component from the proxy request.
+pqus  Proxy Request  URL scheme from the proxy request.
 cquuc Client Request Canonical (prior to remapping) effective URL from client request.
                      It is possible that plugins prior to the remap phase have
                      changed the client request, so this field may not match the
@@ -877,11 +891,11 @@
 
 Some examples below ::
 
-  '%<cqup>'       // the whole characters of <cqup>.
-  '%<cqup>[:]'    // the whole characters of <cqup>.
-  '%<cqup[0:30]>' // the first 30 characters of <cqup>.
-  '%<cqup[-10:]>' // the last 10 characters of <cqup>.
-  '%<cqup[:-5]>'  // everything except the last 5 characters of <cqup>.
+  '%<pqup>'       // the whole characters of <pqup>.
+  '%<pqup>[:]'    // the whole characters of <pqup>.
+  '%<pqup[0:30]>' // the first 30 characters of <pqup>.
+  '%<pqup[-10:]>' // the last 10 characters of <pqup>.
+  '%<pqup[:-5]>'  // everything except the last 5 characters of <pqup>.
 
 Note when ``escape`` in ``format`` is set to ``json``, the start is the
 position before escaping JSON strings, and escaped values are sliced at
diff --git a/doc/admin-guide/logging/understanding.en.rst b/doc/admin-guide/logging/understanding.en.rst
index 35ef485..cf84e1e 100644
--- a/doc/admin-guide/logging/understanding.en.rst
+++ b/doc/admin-guide/logging/understanding.en.rst
@@ -82,8 +82,7 @@
 ``/var/log/messages`` (Linux).
 
 The :manpage:`syslog(8)` process works on a system-wide basis, so it serves as
-the single repository for messages from all |TS| processes (including
-:program:`traffic_server` and :program:`traffic_manager`).
+the single repository for messages from |TS| process (:program:`traffic_server`).
 
 System information logs observe a static format. Each log entry in the log
 contains information about the date and time the error was logged, the hostname
diff --git a/doc/admin-guide/monitoring/error-messages.en.rst b/doc/admin-guide/monitoring/error-messages.en.rst
index 9422569..6aa414f 100644
--- a/doc/admin-guide/monitoring/error-messages.en.rst
+++ b/doc/admin-guide/monitoring/error-messages.en.rst
@@ -118,11 +118,6 @@
    Check your custom log configuration file; there could be syntax
    errors. Refer to :ref:`admin-logging-fields` for correct custom log format fields.
 
-``vip_config binary is not setuid root, manager will be unable to enable virtual ip addresses``
-   The :program:`traffic_manager` process is not able to set virtual IP
-   addresses. You must ``setuid root`` for the ``vip_config`` file in
-   the Traffic Server ``bin`` directory.
-
 .. _body-factory:
 
 HTML Messages Sent to Clients
diff --git a/doc/admin-guide/monitoring/statistics/core/general.en.rst b/doc/admin-guide/monitoring/statistics/core/general.en.rst
index 7ead32c..0144603 100644
--- a/doc/admin-guide/monitoring/statistics/core/general.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/general.en.rst
@@ -34,6 +34,8 @@
 .. ts:stat:: global proxy.node.config.restart_required.manager integer
    :type: flag
 
+   This metric is now deprecated. traffic_manager is no longer supported.
+
 .. ts:stat:: global proxy.node.config.restart_required.proxy integer
    :type: flag
 
@@ -55,8 +57,7 @@
    :type: gauge
    :units: seconds
 
-   Unix epoch-time value indicating the time at which the currently-running
-   :program:`traffic_manager` process was started.
+   This metric is now deprecated. traffic_manager is no longer supported.
 
 .. ts:stat:: global proxy.node.restarts.proxy.cache_ready_time integer
    :type: gauge
@@ -66,13 +67,6 @@
 .. ts:stat:: global proxy.node.restarts.proxy.start_time integer
 .. ts:stat:: global proxy.node.restarts.proxy.stop_time integer
 .. ts:stat:: global proxy.process.user_agent_total_bytes integer
-.. ts:stat:: global proxy.node.version.manager.build_date string
-.. ts:stat:: global proxy.node.version.manager.build_machine string
-.. ts:stat:: global proxy.node.version.manager.build_number integer
-.. ts:stat:: global proxy.node.version.manager.build_person string
-.. ts:stat:: global proxy.node.version.manager.build_time string
-.. ts:stat:: global proxy.node.version.manager.long string
-.. ts:stat:: global proxy.node.version.manager.short float
 .. ts:stat:: global proxy.process.http.tunnels integer
 .. ts:stat:: global proxy.process.update.fails integer
 .. ts:stat:: global proxy.process.update.no_actions integer
diff --git a/doc/admin-guide/monitoring/statistics/core/network-io.en.rst b/doc/admin-guide/monitoring/statistics/core/network-io.en.rst
index 5ea7687..56168ab 100644
--- a/doc/admin-guide/monitoring/statistics/core/network-io.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/network-io.en.rst
@@ -25,10 +25,6 @@
 .. ts:stat:: global proxy.process.net.accepts_currently_open integer
    :type: counter
 
-.. ts:stat:: global proxy.process.net.calls_to_readfromnet_afterpoll integer
-   :type: counter
-   :ungathered:
-
 .. ts:stat:: global proxy.process.net.calls_to_readfromnet integer
    :type: counter
    :ungathered:
@@ -49,10 +45,6 @@
    :type: counter
    :ungathered:
 
-.. ts:stat:: global proxy.process.net.calls_to_writetonet_afterpoll integer
-   :type: counter
-   :ungathered:
-
 .. ts:stat:: global proxy.process.net.calls_to_writetonet integer
    :type: counter
    :ungathered:
diff --git a/doc/admin-guide/performance/index.en.rst b/doc/admin-guide/performance/index.en.rst
index 22db950..b425a2b 100644
--- a/doc/admin-guide/performance/index.en.rst
+++ b/doc/admin-guide/performance/index.en.rst
@@ -331,28 +331,9 @@
 After the connection is established the value of
 :ts:cv:`proxy.config.http.transaction_no_activity_timeout_out` is used to established timeouts on the data over the connection.
 
-In the case where you wish to have a different (generally longer) timeout for
-``POST`` and ``PUT`` connections to an origin server, you may also adjust
-:ts:cv:`proxy.config.http.post_connect_attempts_timeout` which applies only to
-origin connections using those HTTP verbs.
-
 ::
 
     CONFIG proxy.config.http.connect_attempts_timeout INT 30
-    CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800
-
-Parent Proxy Timeout
-~~~~~~~~~~~~~~~~~~~~
-
-In hierarchical caching configurations, the :ts:cv:`proxy.config.http.parent_proxy.connect_attempts_timeout`
-setting is used for all connection attempts to parent caches. It may be useful,
-in cases where you wish to have |TS| fall back to an alternate parent cache
-(in configurations where you have multiple parents for the same cache) more
-quickly, to lower this timeout.
-
-::
-
-    CONFIG  proxy.config.http.parent_proxy.connect_attempts_timeout INT 30
 
 Polling Timeout
 ~~~~~~~~~~~~~~~
diff --git a/doc/admin-guide/plugins/index.en.rst b/doc/admin-guide/plugins/index.en.rst
index 5930843..eb53b1f 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -168,7 +168,6 @@
    Money Trace <money_trace.en>
    MP4 <mp4.en>
    Multiplexer <multiplexer.en>
-   MySQL Remap <mysql_remap.en>
    OpenTelemetry Tracer <otel_tracer.en>
    Parent Select <parent_select.en>
    Rate Limit <rate_limit.en>
@@ -234,9 +233,6 @@
    this can be used to do A/B testing by sending a duplicated slice of inbound production traffic to
    experimental upstreams.
 
-:doc:`MySQL Remap <mysql_remap.en>`
-   Allows dynamic remaps from a MySQL database.
-
 :doc:`OpenTelemetry Tracer <otel_tracer.en>`
    Allows Trafficserver to participate in OpenTelemetry distributed tracing system
 
diff --git a/doc/admin-guide/plugins/lua.en.rst b/doc/admin-guide/plugins/lua.en.rst
index 8ba07fb..29f8a21 100644
--- a/doc/admin-guide/plugins/lua.en.rst
+++ b/doc/admin-guide/plugins/lua.en.rst
@@ -4072,7 +4072,6 @@
     TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES
     TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT
     TS_LUA_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME
-    TS_LUA_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD
     TS_LUA_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS
     TS_LUA_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT
     TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_STR
diff --git a/doc/admin-guide/plugins/mysql_remap.en.rst b/doc/admin-guide/plugins/mysql_remap.en.rst
deleted file mode 100644
index 39ff9bf..0000000
--- a/doc/admin-guide/plugins/mysql_remap.en.rst
+++ /dev/null
@@ -1,91 +0,0 @@
-.. _admin-plugins-mysql-remap:
-
-MySQL Remap Plugin
-******************
-
-.. 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.
-
-.. Note::
-
-    This plugin is *deprecated* as of v9.0.0, and should not be used! The issue
-    is around the blocking APIs used here; If this functionality is needed, you
-    will need to implement it using something non-blocking, such as REDIS.
-
-
-This is a basic plugin for doing dynamic "remaps" from a database. It
-essentially rewrites the incoming request's Host header / origin server
-connection to one retrieved from a database.
-
-The generic proxying setup is the following::
-
-    UA ----> Traffic Server ----> Origin Server
-
-Without the plugin a request like::
-
-    GET /path/to/something HTTP/1.1
-    Host: original.host.com
-
-Ends up requesting ``http://original.host.com/path/to/something``
-
-With this plugin enabled, you can easily change that to anywhere you
-desire. Imagine the many possibilities....
-
-We have benchmarked the plugin with ab at about 9200 requests/sec (1.7k
-object) on a commodity hardware with a local setup of both, MySQL and
-Traffic Server local. Real performance is likely to be substantially
-higher, up to the MySQL's max queries / second.
-
-Installation
-============
-
-This plugin is only built if the configure option ::
-
-    --enable-experimental-plugins
-
-is given at build time.
-
-Configuration
-=============
-
-Import the default schema to a database you create::
-
-    mysql -u root -p -e "CREATE DATABASE mysql_remap;"   # create a new database
-    mysql -u root -p mysql_remap < schema/import.sql     # import the provided schema
-
-insert some interesting values in mysql_remap.hostname &
-mysql_remap.map
-
-Traffic Server plugin configuration is done inside a global
-configuration file: ``/etc/trafficserver/plugin.config``::
-
-    mysql_remap.so /etc/trafficserver/mysql_remap.ini
-
-The INI file should contain the following values::
-
-    [mysql_remap]
-    mysql_host     = localhost   #default
-    mysql_port     = 3306        #default
-    mysql_username = remap_user
-    mysql_password =
-    mysql_database = mysql_remap #default
-
-To debug errors, start trafficserver manually using::
-
-    traffic_server -T "mysql_remap"
-
-And resolve any errors or warnings displayed.
diff --git a/doc/admin-guide/plugins/xdebug.en.rst b/doc/admin-guide/plugins/xdebug.en.rst
index 126028d..d6a2fff 100644
--- a/doc/admin-guide/plugins/xdebug.en.rst
+++ b/doc/admin-guide/plugins/xdebug.en.rst
@@ -41,6 +41,15 @@
 
 This overrides the default ``X-Debug`` header name.
 
+All the debug headers are disabled by default, and you need to enable them
+selectively by passing header names to ``--enable`` option.
+
+    --enable=x-remap,x-cache
+
+This enables ``X-Remap`` and ``X-Cache``. If a client's request has
+``X-Debug: x-remap, x-cache, probe``, XDebug will only injects ``X-Reamp`` and
+``X-Cache``.
+
 
 Debugging Headers
 =================
diff --git a/doc/admin-guide/security/index.en.rst b/doc/admin-guide/security/index.en.rst
index d93b918..97ddb6c 100644
--- a/doc/admin-guide/security/index.en.rst
+++ b/doc/admin-guide/security/index.en.rst
@@ -150,7 +150,7 @@
 
         CONFIG proxy.config.ssl.CA.cert.path STRING /opt/CA/certs/private-ca.pem
 
-#. Run the command :option:`traffic_ctl server restart` to restart |TS|.
+#. Restart Traffic Server.
 
 .. _traffic-server-and-origin-server-connections:
 
@@ -235,7 +235,7 @@
         CONFIG proxy.config.ssl.client.CA.cert.path STRING /opt/ts/etc/ssl/certs/
         CONFIG proxy.config.ssl.client.CA.cert.filename STRING CAs.pem
 
-#. Run the command :option:`traffic_ctl server restart` to restart |TS|.
+#. Restart Traffic Server.
 
 :doc:`mtls.en`
 ==============
diff --git a/doc/appendices/command-line/traffic_crashlog.en.rst b/doc/appendices/command-line/traffic_crashlog.en.rst
index fe11a88..9be4b67 100644
--- a/doc/appendices/command-line/traffic_crashlog.en.rst
+++ b/doc/appendices/command-line/traffic_crashlog.en.rst
@@ -77,10 +77,6 @@
 Caveats
 =======
 
-:program:`traffic_crashlog` makes use of various Traffic Server management
-APIs. If :ref:`traffic_manager` is not available, the crash log will be
-incomplete.
-
 :program:`traffic_crashlog` may generate a crash log containing information you
 would rather not share outside your organization. Please examine the crash log
 carefully before posting it in a public forum.
@@ -89,5 +85,4 @@
 ========
 
 :manpage:`records.config(5)`,
-:manpage:`traffic_manager(8)`,
 :manpage:`traffic_server(8)`
diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst
index 644ffcb..c21fbc9 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -13,7 +13,12 @@
 
 .. include:: ../../common.defs
 
-.. _traffic_ctl:
+.. |RPC| replace:: JSONRPC 2.0
+
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. _JSON: https://www.json.org/json-en.html
+
+.. _traffic_ctl_jsonrpc:
 
 traffic_ctl
 ***********
@@ -23,17 +28,19 @@
 
 :program:`traffic_ctl` [OPTIONS] SUBCOMMAND [OPTIONS]
 
-.. _traffic-ctl-commands:
+
+.. note::
+
+   :program:`traffic_ctl` uses a `JSONRPC`_ protocol to communicate with :program:`traffic_server`.
 
 Description
 ===========
 
-:program:`traffic_ctl` is used to display and manipulate configure
+:program:`traffic_ctl` is used to display, manipulate and configure
 a running Traffic Server. :program:`traffic_ctl` includes a number
 of subcommands that control different aspects of Traffic Server:
 
-:program:`traffic_ctl alarm`
-   Display and manipulate Traffic Server alarms
+
 :program:`traffic_ctl config`
    Manipulate and display configuration records
 :program:`traffic_ctl metric`
@@ -46,8 +53,11 @@
    Interact with plugins.
 :program:`traffic_ctl host`
    Manipulate host status.  parents for now but will be expanded to origins.
+:program:`traffic_ctl rpc`
+   Interact directly with the |RPC| server in |TS|
 
-To use :program:`traffic_ctl`, :ref:`traffic_manager` needs to be running.
+
+
 
 Options
 =======
@@ -61,52 +71,101 @@
 
    Print version information and exit.
 
+.. option:: -f, --format
+
+   Specify the output print style.
+
+   =================== ========================================================================
+   Options             Description
+   =================== ========================================================================
+   ``legacy``          Will honour the old :program:`traffic_ctl` output messages. This is the default format type.
+   ``pretty``          <if available> will print a different output, a prettier output. This depends on the implementation,
+                       it's not required to always implement a pretty output
+   ``json``            It will show the response message formatted to `JSON`_. This is ideal if you want to redirect the stdout to a different source.
+                       It will only stream the json response, no other messages.
+   ``rpc``             Show the JSONRPC request and response + the default output.
+   =================== ========================================================================
+
+   In case of a record request(config) ``--records`` overrides this flag.
+
+   Default: ``legacy``
+
+   Example:
+
+   .. code-block::
+
+      $ traffic_ctl config get variable --format rpc
+      --> {request}
+      <-- {response}
+      variable 1234
+
+   .. code-block::
+
+      $ traffic_ctl config get variable --format json
+      {response}
+
+   There will be no print out beside the json response. This is ideal to redirect to a file.
+
+
+.. option:: --records
+
+   Option available only for records request.
+
+.. option:: --run-root
+
+   Path to the runroot file.
+
 Subcommands
 ===========
 
+.. _traffic-control-command-alarm:
+
 traffic_ctl alarm
 -----------------
-.. program:: traffic_ctl alarm
-.. option:: list
 
-   List all alarm events that have not been acknowledged (cleared).
+.. warning::
 
-.. program:: traffic_ctl alarm
-.. option:: clear
+   Option not available in the |RPC| version.
 
-   Clear (acknowledge) all current alarms.
-
-.. program:: traffic_ctl alarm
-.. option:: resolve ALARM [ALARM...]
-
-   Clear (acknowledge) an alarm event. The arguments are a specific
-   alarm number (e.g. ''1''), or an alarm string identifier (e.g.
-   ''MGMT_ALARM_PROXY_CONFIG_ERROR'').
+.. _traffic-control-command-config:
 
 traffic_ctl config
 ------------------
+
 .. program:: traffic_ctl config
 .. option:: defaults [--records]
 
-   Display the default values for all configuration records. The --records* flag has the same
+   :ref:`admin_lookup_records`
+
+   Display the default values for all configuration records. The ``--records`` flag has the same
    behavior as :option:`traffic_ctl config get --records`.
 
 .. program:: traffic_ctl config
 .. option:: describe RECORD [RECORD...]
 
+   :ref:`admin_lookup_records`
+
    Display all the known information about a configuration record. This includes the current and
    default values, the data type, the record class and syntax checking expression.
 
+   Error output available if  ``--format pretty`` is specified.
+
 .. program:: traffic_ctl config
 .. option:: diff [--records]
 
-   Display configuration records that have non-default values. The --records* flag has the same
+   :ref:`admin_lookup_records`
+
+   Display configuration records that have non-default values. The ``--records`` flag has the same
    behavior as :option:`traffic_ctl config get --records`.
 
 .. program:: traffic_ctl config
 .. option:: get [--records] RECORD [RECORD...]
 
-   Display the current value of a configuration record.
+   :ref:`admin_lookup_records`
+
+Display the current value of a configuration record.
+
+   Error output available if ``--format pretty`` is specified.
 
 .. program:: traffic_ctl config get
 .. option:: --records
@@ -117,13 +176,17 @@
 .. program:: traffic_ctl config
 .. option:: match [--records] REGEX [REGEX...]
 
+   :ref:`admin_lookup_records`
+
    Display the current values of all configuration variables whose names match the given regular
-   expression. The *--records* flag has the same behavior as :option:`traffic_ctl config get
+   expression. The ``--records`` flag has the same behavior as :option:`traffic_ctl config get
    --records`.
 
 .. program:: traffic_ctl config
 .. option:: reload
 
+   :ref:`admin_config_reload`
+
    Initiate a Traffic Server configuration reload. Use this command to update the running
    configuration after any configuration file modification. If no configuration files have been
    modified since the previous configuration load, this command is a no-op.
@@ -134,6 +197,8 @@
 .. program:: traffic_ctl config
 .. option:: set RECORD VALUE
 
+   :ref:`admin_config_set_records`
+
    Set the named configuration record to the specified value. Refer to the :file:`records.config`
    documentation for a list of the configuration variables you can specify. Note that this is not a
    synchronous operation.
@@ -141,113 +206,128 @@
 .. program:: traffic_ctl config
 .. option:: status
 
+   :ref:`admin_lookup_records`
+
    Display detailed status about the Traffic Server configuration system. This includes version
    information, whether the internal configuration store is current and whether any daemon processes
    should be restarted.
 
-.. _traffic-ctl-metric:
+.. program:: traffic_ctl config
+.. option:: registry
+
+   :ref:`filemanager.get_files_registry`
+
+   Display information about the registered files in |TS|. This includes the full file path, config record name, parent config (if any)
+   if needs root access and if the file is required in |TS|.
+
+.. _traffic-control-command-metric:
 
 traffic_ctl metric
 ------------------
+
 .. program:: traffic_ctl metric
 .. option:: get METRIC [METRIC...]
 
-   Display the current value of the specifies statistics.
+   :ref:`admin_lookup_records`
+
+   Display the current value of the specified statistics.
+
+   Error output available if ``--format pretty`` is specified.
 
 .. program:: traffic_ctl metric
 .. option:: match REGEX [REGEX...]
 
+   :ref:`admin_lookup_records`
+
    Display the current values of all statistics whose names match
    the given regular expression.
 
 .. program:: traffic_ctl metric
 .. option:: zero METRIC [METRIC...]
 
+   :ref:`admin_clear_metrics_records`
+
    Reset the named statistics to zero.
 
+.. program:: traffic_ctl metric
+.. option:: describe RECORD [RECORD...]
+
+   :ref:`admin_lookup_records`
+
+   Display all the known information about a metric record.
+
+   Error output available if ``--format pretty`` is specified.
+
+.. _traffic-control-command-server:
+
 traffic_ctl server
 ------------------
-.. program:: traffic_ctl server
-.. option:: restart
-
-   Shut down and immediately restart Traffic Server
-
-.. program:: traffic_ctl server restart
-.. option:: --drain
-
-   This option modifies the behavior of :option:`traffic_ctl server restart` such that
-   :program:`traffic_server` is not shut down until the number of active client connections drops to
-   the number given by the :ts:cv:`proxy.config.restart.active_client_threshold` configuration
-   variable.
-
-.. option:: --manager
-
-   The default behavior of :option:`traffic_ctl server restart` is to restart
-   :program:`traffic_server`. If this option is specified, :program:`traffic_manager` is also
-   restarted.
 
 .. program:: traffic_ctl server
-.. option:: start
 
-   Start :program:`traffic_server` if it is already running.
+.. program:: traffic_ctl server
+.. option:: drain
 
-.. program:: traffic_ctl server start
-.. option:: --clear-cache
+   :ref:`admin_server_start_drain`
 
-   Clear the disk cache upon startup.
+   :ref:`admin_server_stop_drain`
 
-.. option:: --clear-hostdb
-
-   Clear the DNS resolver cache upon startup.
+   Drop the number of active client connections.
 
 .. program:: traffic_ctl server
 .. option:: status
 
-   Show the current proxy server status, indicating if we're running or not.
+   Option not yet available
 
-.. program:: traffic_ctl server
-.. option:: stop
-
-   Stop the running :program:`traffic_server` process.
-
-.. program:: traffic_ctl server
-.. option:: backtrace
-
-   Show a full stack trace of all the :program:`traffic_server` threads.
+.. _traffic-control-command-storage:
 
 traffic_ctl storage
 -------------------
+
 .. program:: traffic_ctl storage
 .. option:: offline PATH [PATH ...]
 
+   :ref:`admin_storage_set_device_offline`
+
    Mark a cache storage device as offline. The storage is identified by :arg:`PATH` which must match
    exactly a path specified in :file:`storage.config`. This removes the storage from the cache and
    redirects requests that would have used this storage to other storage. This has exactly the same
    effect as a disk failure for that storage. This does not persist across restarts of the
    :program:`traffic_server` process.
 
+.. program:: traffic_ctl storage
+.. option:: status PATH [PATH ...]
+
+   :ref:`admin_storage_get_device_status`
+
+   Show the storage configuration status.
+
+.. _traffic-control-command-plugin:
+
 traffic_ctl plugin
 -------------------
+
 .. program:: traffic_ctl plugin
 .. option:: msg TAG DATA
 
+   :ref:`admin_plugin_send_basic_msg`
+
    Send a message to plugins. All plugins that have hooked the
-   :cpp:enumerator:`TSLifecycleHookID::TS_LIFECYCLE_MSG_HOOK` will receive a callback for that hook.
+   ``TSLifecycleHookID::TS_LIFECYCLE_MSG_HOOK`` will receive a callback for that hook.
    The :arg:`TAG` and :arg:`DATA` will be available to the plugin hook processing. It is expected
    that plugins will use :arg:`TAG` to select relevant messages and determine the format of the
-   :arg:`DATA`. The :arg:`DATA` is optional and may not be available to consume, if not available then size will be 0
-   and the data will be NULL. Any extra passed value beside the tag and the optional data will be ignored.
-   Check :c:type:`TSPluginMsg` for more info.
+   :arg:`DATA`.
+
+.. _traffic-control-command-host:
 
 traffic_ctl host
 ----------------
 .. program:: traffic_ctl host
 
-A stat to track status is created for each host. The name is the host fqdn with a prefix of
-"proxy.process.host_status". The value of the stat is a string which is the serialized
-representation of the status. This contains the overall status and the status for each reason.  The
-stats may be viewed using the :program:`traffic_ctl metric` command or through the `stats_over_http`
-endpoint.
+A record to track status is created for each host. The name is the host fqdn.  The value of the
+record when retrieved, is a serialized string representation of the status.
+This contains the overall status and the status for each reason.  The
+records may be viewed using the :program:`traffic_ctl host status` command.
 
 .. option:: --time count
 
@@ -281,11 +361,15 @@
 
 .. option:: status HOSTNAME [HOSTNAME ...]
 
+   :ref:`admin_lookup_records`
+
    Get the current status of the specified hosts with respect to their use as targets for parent
-   selection. This returns the same information as the per host stat.
+   selection. If the HOSTNAME arguments are omitted, all host records available are returned.
 
 .. option:: down HOSTNAME [HOSTNAME ...]
 
+   :ref:`admin_host_set_status`
+
    Marks the listed hosts as down so that they will not be chosen as a next hop parent. If
    :option:`--time` is included the host is marked down for the specified number of seconds after
    which the host will automatically be marked up. A host is not marked up until all reason codes
@@ -295,6 +379,8 @@
 
 .. option:: up HOSTNAME [HOSTNAME ...]
 
+   :ref:`admin_host_set_status`
+
    Marks the listed hosts as up so that they will be available for use as a next hop parent. Use
    :option:`--reason` to mark the host reason code. The 'self_detect' is an internal reason code
    used by parent selection to mark down a parent when it is identified as itself and
@@ -302,32 +388,220 @@
 
    Supports :option:`--reason`.
 
+.. _traffic_ctl_rpc:
+
+traffic_ctl rpc
+---------------
+.. program:: traffic_ctl rpc
+
+A mechanism to interact directly with the |TS| |RPC| endpoint. This means that this is not tied to any particular API
+but rather to the rpc endpoint, so you can directly send requests and receive responses from the server.
+
+.. option:: file
+
+   Reads a file or a set of files from the disc, use the content of the files as message(s) to the |RPC| endpoint. All jsonrpc messages
+   will be validated before sending them. If the file contains invalid  json|yaml format the message will not be send, in
+   case of a set of files, if a particular file is not a proper json/yaml format then that particular file will be skipped.
+
+   Example:
+
+   .. code-block:: bash
+
+      traffic_ctl rpc file jsonrpc_cmd1.json jsonrpc_cmd2.yaml
+
+.. option:: get-api
+
+   :ref:`show_registered_handlers`
+
+   Request the entire admin api. This will retrieve all the registered methods and notifications on the server side.
+
+   Example:
+
+   .. code-block:: bash
+
+      $ traffic_ctl rpc get-api
+      Methods:
+      - admin_host_set_status
+      - admin_server_stop_drain
+      - admin_server_start_drain
+      - admin_clear_metrics_records
+      - admin_clear_all_metrics_records
+      - admin_plugin_send_basic_msg
+      - admin_lookup_records
+      - admin_config_set_records
+      - admin_storage_get_device_status
+      - admin_storage_set_device_offline
+      - admin_config_reload
+      - show_registered_handlers
+      Notifications:
+      - some_registered_notification_handler
+
+
+.. option:: input
+
+   Input mode, traffic_ctl will provide a control input from a stream buffer. Once the content is written the terminal :program:`traffic_ctl`
+   will wait for the user to press Control-D to send the request to the rpc endpoint.
+   This feature allows you to directly interact with the jsonrpc endpoint and test your API easily and without the need to know the low level
+   implementation details of the transport layer.
+   :program:`traffic_ctl` will validate the input format, not the message content. The message content will be validated by the server.
+   See example `input_example_2`_.
+
+   .. option:: --raw, -r
+
+      No json/yaml parse validation will take place, the input content will be directly send to the server.
+
+   Example:
+
+   .. code-block::
+
+      $ traffic_ctl rpc input
+      >> Ctrl-D to fire the request
+      {
+         "id":"86e59b43-185b-4a0b-b1c1-babb1a3d5401",
+         "jsonrpc":"2.0",
+         "method":"admin_lookup_records",
+         "params":[
+            {
+               "record_name":"proxy.config.diags.debug.tags",
+               "rec_types":[
+                  "1",
+                  "16"
+               ]
+            }
+         ]
+      }
+      <pressed Ctrl-D>
+
+      <-- Server's response.
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[
+               {
+                  "record":{
+                     "record_name":"proxy.config.diags.debug.tags",
+                     "record_type":"3",
+                     "version":"0",
+                     "raw_stat_block":"0",
+                     "order":"423",
+                     "config_meta":{
+                        "access_type":"0",
+                        "update_status":"0",
+                        "update_type":"1",
+                        "checktype":"0",
+                        "source":"3",
+                        "check_expr":"null"
+                     },
+                     "record_class":"1",
+                     "overridable":"false",
+                     "data_type":"STRING",
+                     "current_value":"rpc",
+                     "default_value":"http|dns"
+                  }
+               }
+            ]
+         },
+         "id":"86e59b43-185b-4a0b-b1c1-babb1a3d5401"
+      }
+
+
+.. _input_example_2:
+
+   Example 2:
+
+   You can see a valid  json ``{}`` but an invalid |RPC| message. In this case the server is responding.
+
+   .. code-block::
+
+      $ traffic_ctl rpc input
+      >> Ctrl-D to fire the request
+      {}
+      <pressed Ctrl-D>
+      < -- Server's response
+      {
+         "jsonrpc":"2.0",
+         "error":{
+            "code":-32600,
+            "message":"Invalid Request"
+         }
+      }
+
+
+.. option:: invoke
+
+   Invoke a remote call by using the method name as parameter. This could be a handy option if you are developing a new handler or you
+   just don't want to expose the method in :program:`traffic_ctl`, for instance when implementing a custom handler inside a proprietary plugin.
+
+   .. option:: --params, -p
+
+      Parameters to be passed in the request, YAML or JSON format are accepted. If JSON is passed as param it should not
+      be mixed with YAML. It's important that you follow the :ref:`jsonrpc-protocol` specs. If the passed param does not
+      follows the specs the server will reject the request.
+
+.. _rpc_invoke_example_1:
+
+   Example 1:
+
+   Call a jsonrpc method with no parameter.
+
+   .. code-block::
+
+      $ traffic_ctl rpc invoke some_jsonrpc_handler
+      --> {"id": "0dbab88d-b78f-4ebf-8aa3-f100031711a5", "jsonrpc": "2.0", "method": "some_jsonrpc_handler"}
+      <-- { response }
+
+.. _rpc_invoke_example_2:
+
+   Example 2:
+
+   Call a jsonrpc method with parameters.
+
+   .. code-block::
+
+      $ traffic_ctl rpc invoke reload_files_from_folder --params 'filenames: ["file1", "file2"]' 'folder: "/path/to/folder"'
+      --> {"id": "9ac68652-5133-4d5f-8260-421baca4c67f", "jsonrpc": "2.0", "method": "reload_files_from_folder", "params": {"filenames": ["file1", "file2"], "folder": "/path/to/folder"}}
+      <-- { response }
+
 Examples
 ========
 
 Mark down a host with `traffic_ctl` and view the associated host stats::
 
-   $ traffic_ctl host down cdn-cache-02.foo.com --reason manual
+   .. code-block:: bash
 
-   $ /opt/trafficserver/bin/traffic_ctl metric match host_status
-   proxy.process.host_status.cdn-cache-01.foo.com HOST_STATUS_DOWN,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:DOWN:1556896844:0,SELF_DETECT:UP:0
-   proxy.process.host_status.cdn-cache-02.foo.com HOST_STATUS_UP,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:UP:0:0,SELF_DETECT:UP:0
-   proxy.process.host_status.cdn-cache-origin-01.foo.com HOST_STATUS_UP,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:UP:0:0,SELF_DETECT:UP:0
+      # traffic_ctl host down cdn-cache-02.foo.com --reason manual
+
+      # traffic_ctl metric match host_status
+      proxy.process.host_status.cdn-cache-01.foo.com HOST_STATUS_DOWN,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:DOWN:1556896844:0,SELF_DETECT:UP:0
+      proxy.process.host_status.cdn-cache-02.foo.com HOST_STATUS_UP,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:UP:0:0,SELF_DETECT:UP:0
+      proxy.process.host_status.cdn-cache-origin-01.foo.com HOST_STATUS_UP,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:UP:0:0,SELF_DETECT:UP:0
 
 In the example above, 'cdn-cache-01.foo.com' is unavailable, `HOST_STATUS_DOWN` and was marked down
 for the `manual` reason, `MANUAL:DOWN:1556896844:0`, at the time indicated by the UNIX time stamp
-`1556896844`.  To make the host available, one would have to clear the `manual` reason using ::
+`1556896844`.  To make the host available, one would have to clear the `manual` reason using:
 
-   $ traffic_ctl host up cdn-cache-01.foo.com --reason manual
+   .. code-block:: bash
 
-Configure Traffic Server to insert ``Via`` header in the response to
-the client::
+      # traffic_ctl host up cdn-cache-01.foo.com --reason manual
 
-   $ traffic_ctl config set proxy.config.http.insert_response_via_str 1
-   $ traffic_ctl config reload
+Configure Traffic Server to insert ``Via`` header in the response to the client
+
+   .. code-block:: bash
+
+      # traffic_ctl config set proxy.config.http.insert_response_via_str 1
+      # traffic_ctl config reload
+
+Autest
+======
+
+Runroot needs to be configured in order to let `traffic_ctl` know where to find the socket. This is done by default
+and there is no change you have to do to interact with it, but make sure that you are not overriding the `dump_runroot=False`
+when creating the ATS Process, otherwise the `runroot.yaml` will not be set.
 
 See also
 ========
 
 :manpage:`records.config(5)`,
-:manpage:`storage.config(5)`
+:manpage:`storage.config(5)`,
+:ref:`admin-jsonrpc-configuration`,
+:ref:`jsonrpc-protocol`
diff --git a/doc/appendices/command-line/traffic_layout.en.rst b/doc/appendices/command-line/traffic_layout.en.rst
index f4fa104..2ad1a3b 100644
--- a/doc/appendices/command-line/traffic_layout.en.rst
+++ b/doc/appendices/command-line/traffic_layout.en.rst
@@ -50,12 +50,12 @@
 A runroot will be created in ``/path/to/runroot``, available for other programs to use.
 If the path is not specified, the current working directory will be used.
 
-For example, to run traffic_manager, using the runroot, there are several ways:
-    #. ``/path/to/runroot/bin/traffic_manager``
-    #. ``traffic_manager --run-root=/path/to/runroot``
-    #. ``traffic_manager --run-root=/path/to/runroot/runroot.yaml``
-    #. Set :envvar:`TS_RUNROOT` to ``/path/to/runroot`` and run ``traffic_manager``
-    #. Run ``traffic_manager`` with ``/path/to/runroot`` as current working directory
+For example, to run traffic_server, using the runroot, there are several ways:
+    #. ``/path/to/runroot/bin/traffic_server``
+    #. ``traffic_server --run-root=/path/to/runroot``
+    #. ``traffic_server --run-root=/path/to/runroot/runroot.yaml``
+    #. Set :envvar:`TS_RUNROOT` to ``/path/to/runroot`` and run ``traffic_server``
+    #. Run ``traffic_server`` with ``/path/to/runroot`` as current working directory
 
 .. Note::
 
diff --git a/doc/appendices/command-line/traffic_manager.en.rst b/doc/appendices/command-line/traffic_manager.en.rst
index 90ae56d..bf21413 100644
--- a/doc/appendices/command-line/traffic_manager.en.rst
+++ b/doc/appendices/command-line/traffic_manager.en.rst
@@ -22,65 +22,9 @@
 traffic_manager
 ***************
 
-.. program:: traffic_manager
+.. important::
 
-Description
-===========
-
-.. option:: --aconfPort PORT
-.. option:: --action TAGS
-.. option:: --debug TAGS
-.. option:: --groupAddr ADDRESS
-.. option:: --help
-.. option:: --nosyslog
-.. option:: --path FILE
-.. option:: --proxyOff
-.. option:: --listenOff
-.. option:: --proxyPort PORT
-.. option:: --recordsConf FILE
-.. option:: --tsArgs ARGUMENTS
-.. option:: --maxRecords RECORDS
-.. option:: --bind_stdout FILE
-
-The file to which the stdout stream for :program:`traffic_manager` will be bound.
-
-.. option:: --bind_stderr FILE
-
-The file to which the stderr stream for :program:`traffic_manager` will be bound.
-
-.. option:: --version
-
-Signals
-=======
-
-SIGHUP
-  This signal causes a reconfiguration event, equivalent to running :program:`traffic_ctl config reload`.
-
-SIGINT, SIGTERM
-  These signals cause :program:`traffic_manager` to exit after also shutting down :program:`traffic_server`.
-
-SIGUSR2
-  This signal causes the :program:`traffic_manager` and :program:`traffic_server` processes to close
-  and reopen their file descriptors for all of their log files. This allows the use of external
-  tools to handle log rotation and retention. For instance, logrotate(8) can be configured to rotate
-  the various |ATS| logs and, via the logrotate postrotate script, send a `-SIGUSR2` to the
-  :program:`traffic_manager` process. After the signal is received, |ATS| will stop logging to the
-  now-rolled files and will reopen log files with the originally configured log names.
-
-Exponential Back-off Delay
-==========================
-
-  If :program:`traffic_server` has issues communicating with  :program:`traffic_manager` after a crash,
-  :program:`traffic_manager` will retry to start  :program:`traffic_server` using an exponential back-off delay,
-  which will make :program:`traffic_manager` to retry starting :program:`traffic_server` from ``1s`` until it
-  reaches the max ceiling time. The ceiling time is configurable  as well as the number of times that
-  :program:`traffic_manager` will keep trying to start :program:`traffic_server`. *A random variance will be
-  added to the sleep time on every retry*
-
-.. note::
-  For more information about this configuration please check :file:`records.config`
-
-
+  traffic_manager is deprecated. :program:`traffic_server` must be used instead.
 
 See also
 ========
diff --git a/doc/appendices/command-line/traffic_server.en.rst b/doc/appendices/command-line/traffic_server.en.rst
index edcd51d..af88c6d 100644
--- a/doc/appendices/command-line/traffic_server.en.rst
+++ b/doc/appendices/command-line/traffic_server.en.rst
@@ -119,11 +119,6 @@
 The maximum number of entries in metrics and configuration variables. The default is 1600, which is
 also the minimum. This may need to be increased if running plugins that create metrics.
 
-.. option:: -M, --remote_management
-
-Indicates the process should expect to be managed by :ref:`traffic_manager`. This option should not
-be used except by that process.
-
 .. option:: -n COUNT, --net_threads COUNT
 
 .. option:: -k, --clear_hostdb
@@ -149,11 +144,6 @@
 Environment
 ===========
 
-.. envvar:: PROXY_REMOTE_MGMT
-
-This environment variable forces :program:`traffic_server` to believe that it is being managed by
-:program:`traffic_manager`.
-
 .. envvar:: PROXY_AUTO_EXIT
 
 When this environment variable is set to an integral number of
@@ -176,4 +166,3 @@
 ========
 
 :manpage:`traffic_ctl(8)`,
-:manpage:`traffic_manager(8)`
diff --git a/doc/appendices/faq.en.rst b/doc/appendices/faq.en.rst
index 1cca514..ea24873 100644
--- a/doc/appendices/faq.en.rst
+++ b/doc/appendices/faq.en.rst
@@ -329,31 +329,14 @@
 
 :program:`traffic_ctl` commands do not execute under the following conditions:
 
-**When the traffic_manager process is not running**
-    Check to see if the :program:`traffic_manager` process is running by entering the
-    following command::
+**When the traffic_server process is not running**
+    :program:`traffic_ctl` needs traffic_server to be running, the former needs
+    the later to be running in order to connect to the jsonrpc endpoint. You can
+    check this :ref:`jsonrpc-node` for more information.
 
-        pgrep -l traffic_manager
-
-    If the :program:`traffic_manager` process is not running, then enter the
-    following command from the Traffic Server ``bin`` directory to start it::
-
-        ./traffic_manager
-
-.. XXX: this is wrong
-
-    You should always start and stop Traffic Server with the
-    :program:`trafficserver start` and :program:`trafficserver stop` commands to ensure
-    that all the processes start and stop correctly. For more information,
-    refer to :ref:`getting-started`.
-
-**When you are not executing the command from $TSHome/bin**
-    If the Traffic Server ``bin`` directory is not in your path, then prepend the
-    Traffic Control commands with ``./`` (for example, ``./traffic_ctl -h``).
-
-**When multiple Traffic Server installations are present and you are not
-executing the Traffic Control command from the active Traffic Server path
-specified in ``/etc/trafficserver``**
+**When :program:`traffic_ctl` is unable to locate the jsonrpc socket.**
+    The program needs to know where the path is located, this could be the default
+    build folder or the one specified by the runroot file.
 
 
 Web browsers display an error document with a 'data missing' message
@@ -525,7 +508,7 @@
 URLs in the entry.  You can truncate long URLs using the slice syntax for the URL log field in the log
 format, for example::
 
-    %<cquc[:1000]>
+    %<pquc[:1000]>
 
 You can also increase the value of :ts:cv:`proxy.config.log.log_buffer_size`, but this can have impacts
 on performance and memory usage.  Very large values may trigger software bugs.  Some production proxies
diff --git a/doc/developer-guide/api/functions/TSContSchedule.en.rst b/doc/developer-guide/api/functions/TSContSchedule.en.rst
deleted file mode 100644
index 4dbb9f6..0000000
--- a/doc/developer-guide/api/functions/TSContSchedule.en.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-.. 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
-
-.. default-domain:: c
-
-TSContSchedule
-**************
-
-Synopsis
-========
-
-.. code-block:: cpp
-
-    #include <ts/ts.h>
-
-.. function:: TSAction TSContSchedule(TSCont contp, TSHRTime timeout)
-
-Description
-===========
-
-Schedules :arg:`contp` to run :arg:`delay` milliseconds in the future. This is approximate. The delay
-will be at least :arg:`delay` but possibly more. Resolutions finer than roughly 5 milliseconds will
-not be effective. :arg:`contp` is required to have a mutex, which is provided to
-:func:`TSContCreate`.
-
-The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is
-effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on
-another thread this can be problematic to be correctly timed. The return value can be checked with
-:func:`TSActionDone` to see if the continuation ran before the return, which is possible if
-:arg:`timeout` is `0`. Returns ``nullptr`` if thread affinity was cleared.
-
-TSContSchedule() or TSContScheduleEvery() will default to set the thread affinity to the calling thread
-when no affinity is already set for example, using :func:`TSContThreadAffinitySet`
-
-Note that the TSContSchedule() family of API shall only be called from an ATS EThread.
-Calling it from raw non-EThreads can result in unpredictable behavior.
-
-See Also
-========
-
-:doc:`TSContScheduleEvery.en`
-:doc:`TSContScheduleOnPool.en`
-:doc:`TSContScheduleOnThread.en`
diff --git a/doc/developer-guide/api/functions/TSContScheduleEvery.en.rst b/doc/developer-guide/api/functions/TSContScheduleEvery.en.rst
deleted file mode 100644
index bc61392..0000000
--- a/doc/developer-guide/api/functions/TSContScheduleEvery.en.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-.. 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
-
-.. default-domain:: c
-
-TSContScheduleEvery
-*******************
-
-Synopsis
-========
-
-.. code-block:: cpp
-
-    #include <ts/ts.h>
-
-.. function:: TSAction TSContScheduleEvery(TSCont contp, TSHRTime every)
-
-Description
-===========
-
-Schedules :arg:`contp` to periodically run every :arg:`delay` milliseconds in the future.
-This is approximate. The delay will be at least :arg:`delay` but possibly more.
-Resolutions finer than roughly 5 milliseconds will not be effective. :arg:`contp` is
-required to have a mutex, which is provided to :func:`TSContCreate`.
-
-The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is
-effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on
-another thread this can be problematic to be correctly timed. The return value can be checked with
-:func:`TSActionDone` to see if the continuation ran before the return, which is possible if
-:arg:`timeout` is `0`. Returns ``nullptr`` if thread affinity was cleared.
-
-TSContSchedule() or TSContScheduleEvery() will default to set the thread affinity to the calling thread
-when no affinity is already set for example, using :func:`TSContThreadAffinitySet`
-
-Note that the TSContSchedule() family of API shall only be called from an ATS EThread.
-Calling it from raw non-EThreads can result in unpredictable behavior.
-
-See Also
-========
-
-:doc:`TSContSchedule.en`
-:doc:`TSContScheduleOnPool.en`
-:doc:`TSContScheduleOnThread.en`
diff --git a/doc/developer-guide/api/functions/TSContScheduleEveryOnPool.en.rst b/doc/developer-guide/api/functions/TSContScheduleEveryOnPool.en.rst
new file mode 100644
index 0000000..0116395
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSContScheduleEveryOnPool.en.rst
@@ -0,0 +1,58 @@
+.. 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
+
+.. default-domain:: c
+
+TSContScheduleEveryOnPool
+*************************
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+    #include <ts/ts.h>
+
+.. function:: TSAction TSContScheduleEveryOnPool(TSCont contp, TSHRTime every)
+
+Description
+===========
+
+Schedules :arg:`contp` to run :arg:`every` milliseconds, on a random thread that belongs to
+:arg:`tp`. The :arg:`every` is an approximation, meaning it will be at least :arg:`every`
+milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will not be
+effective. Note that :arg:`contp` is required to have a mutex, which is provided to
+:func:`TSContCreate`.
+
+The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This
+is effective until the continuation :arg:`contp` is being dispatched. However, if it is
+scheduled on another thread this can be problematic to be correctly timed. The return value
+can be checked with :func:`TSActionDone` to see if the continuation ran before the return,
+which is possible if :arg:`timeout` is `0`.
+
+If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set
+as its thread affinity thread.
+
+Note that the TSContSchedule() family of API shall only be called from an ATS EThread.
+Calling it from raw non-EThreads can result in unpredictable behavior.
+
+See Also
+========
+
+:doc:`TSContScheduleOnPool.en`
+:doc:`TSContScheduleOnThread.en`
diff --git a/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst b/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst
index 1009187..e044e4f 100644
--- a/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst
+++ b/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst
@@ -33,11 +33,16 @@
 Description
 ===========
 
-Mostly the same as :func:`TSContSchedule`. Schedules :arg:`contp` on a random thread that belongs to :arg:`tp`.
-If thread type of the thread specified by thread affinity is the same as :arg:`tp`, the :arg:`contp` will
-be scheduled on the thread specified by thread affinity.
+Schedules :arg:`contp` to run :arg:`timeout` milliseconds in the future, on a random thread that
+belongs to :arg:`tp`. The :arg:`timeout` is an approximation, meaning it will be at least
+:arg:`timeout` milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will
+not be effective. Note that :arg:`contp` is required to have a mutex, which is provided to
+:func:`TSContCreate`.
 
-The continuation is scheduled for a particular thread selected from a group of similar threads, as indicated by :arg:`tp`.
+The continuation is scheduled for a particular thread selected from a group of similar threads,
+as indicated by :arg:`tp`. If :arg:`contp` already has an thread affinity set, and the thread
+type of thread affinity is the same as :arg:`tp`, then :arg:`contp` will be scheduled on the
+thread specified by thread affinity.
 
 =========================== =======================================================================================
 Pool                        Properties
@@ -54,6 +59,15 @@
 are threads that exist to perform long or blocking actions, although sufficiently long operation can
 impact system performance by blocking other continuations on the threads.
 
+The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is
+effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on
+another thread this can be problematic to be correctly timed. The return value can be checked with
+:func:`TSActionDone` to see if the continuation ran before the return, which is possible if
+:arg:`timeout` is `0`.
+
+If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set
+as its thread affinity thread.
+
 Note that the TSContSchedule() family of API shall only be called from an ATS EThread.
 Calling it from raw non-EThreads can result in unpredictable behavior.
 
@@ -129,7 +143,6 @@
 See Also
 ========
 
-:doc:`TSContSchedule.en`
-:doc:`TSContScheduleEvery.en`
+:doc:`TSContScheduleEveryOnPool.en`
 :doc:`TSContScheduleOnThread.en`
 :doc:`TSLifecycleHookAdd.en`
diff --git a/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst b/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst
index 0f91166..41148a9 100644
--- a/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst
+++ b/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst
@@ -33,7 +33,19 @@
 Description
 ===========
 
-Mostly the same as :func:`TSContSchedule`. Schedules :arg:`contp` on :arg:`ethread`.
+Schedules :arg:`contp` to run :arg:`timeout` milliseconds in the future, on the thread specified by
+:arg:`ethread`. The :arg:`timeout` is an approximation, meaning it will be at least :arg:`timeout`
+milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will not be effective.
+Note that :arg:`contp` is required to have a mutex, which is provided to :func:`TSContCreate`.
+
+The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is
+effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on
+another thread this can be problematic to be correctly timed. The return value can be checked with
+:func:`TSActionDone` to see if the continuation ran before the return, which is possible if
+:arg:`timeout` is `0`.
+
+If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set
+as its thread affinity thread.
 
 Note that the TSContSchedule() family of API shall only be called from an ATS EThread.
 Calling it from raw non-EThreads can result in unpredictable behavior.
@@ -41,6 +53,5 @@
 See Also
 ========
 
-:doc:`TSContSchedule.en`
-:doc:`TSContScheduleEvery.en`
 :doc:`TSContScheduleOnPool.en`
+:doc:`TSContScheduleEveryOnPool.en`
diff --git a/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst b/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst
index 10465e0..64417de 100644
--- a/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst
+++ b/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst
@@ -34,10 +34,10 @@
 ===========
 
 Set the thread affinity of continuation :arg:`contp` to :arg:`ethread`. Future calls to
-:func:`TSContSchedule`, and :func:`TSContScheduleOnPool` that has the same type as :arg:`ethread`
+:func:`TSContScheduleOnPool`, and :func:`TSContScheduleOnPool` that has the same type as :arg:`ethread`
 will schedule the continuation on :arg:`ethread`, rather than an arbitrary thread of that type.
 
-:func:`TSContSchedule` and :func:`TSContScheduleEvery` will default the affinity to calling thread
+:func:`TSContScheduleOnPool` and :func:`TSContScheduleEveryOnPool` will default the affinity to calling thread
 when invoked without explicitly setting the thread affinity.
 
 Return Values
diff --git a/doc/developer-guide/api/functions/TSHttpArgs.en.rst b/doc/developer-guide/api/functions/TSHttpArgs.en.rst
deleted file mode 100644
index a564eb2..0000000
--- a/doc/developer-guide/api/functions/TSHttpArgs.en.rst
+++ /dev/null
@@ -1,102 +0,0 @@
-.. 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
-.. default-domain:: c
-
-
-TSHttpArgs
-**********
-
-Synopsis
-========
-
-.. note::
-
-   This set of API is obsoleted as of ATS v9.0.0, and will be removed with ATS v10.0.0!
-   For details of the new APIs, see :ref:`tsuserargs`.
-
-
-.. code-block:: cpp
-
-    #include <ts/ts.h>
-
-.. function:: TSReturnCode TSHttpTxnArgIndexReserve(const char * name, const char * description, int * arg_idx)
-.. function:: TSReturnCode TSHttpTxnArgIndexNameLookup(const char * name, int * arg__idx, const char ** description)
-.. function:: TSReturnCode TSHttpTxnArgIndexLookup(int arg_idx, const char ** name, const char ** description)
-.. function:: TSReturnCode TSHttpSsnArgIndexReserve(const char * name, const char * description, int * arg_idx)
-.. function:: TSReturnCode TSHttpSsnArgIndexNameLookup(const char * name, int * arg__idx, const char ** description)
-.. function:: TSReturnCode TSHttpSsnArgIndexLookup(int arg_idx, const char ** name, const char ** description)
-.. function:: void TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void * arg)
-.. function:: void * TSHttpTxnArgGet(TSHttpTxn txnp, int arg_idx)
-.. function:: void TSHttpSsnArgSet(TSHttpSsn ssnp, int arg_idx, void * arg)
-.. function:: void * TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx)
-
-Description
-===========
-
-|TS| sessions and transactions provide a fixed array of void pointers that can be used by plugins to
-store information. This can be used to avoid creating a per session or transaction continuations to
-hold data, or to communicate between plugins as the values in the array are visible to any plugin
-which can access the session or transaction. The array values are opaque to |TS| and it will not
-dereference nor release them. Plugins are responsible for cleaning up any resources pointed to by the
-values or, if the values are simply values, there is no need for the plugin to remove them after the
-session or transaction has completed.
-
-To avoid collisions between plugins a plugin should first *reserve* an index in the array. A
-transaction based plugin argument is reserved by calling :func:`TSHttpTxnArgIndexReserve`. A session
-base plugin argument is reserved by calling :func:`TSHttpSsnArgIndexReserve`. Both functions have the arguments
-
-:arg:`name`
-   An identifying name for the plugin that reserved the index. Required.
-
-:arg:`description`
-   An optional description of the use of the arg. This can be :code:`nullptr`.
-
-:arg:`arg_idx`
-   A pointer to an :code:`int`. If an index is successfully reserved, the :code:`int` pointed at by this is
-   set to the reserved index. It is not modified if the call is unsuccessful.
-
-The functions return :code:`TS_SUCCESS` if an index was reserved,
-:code:`TS_ERROR` if not (most likely because all of the indices have already been reserved).
-Generally this will be a file or library scope global which is set at plugin initialization. This
-function is used in the example remap plugin :ts:git:`example/plugins/c-api/remap/remap.cc`. The index is stored
-in the plugin global :code:`arg_index`. Transaction and session plugin argument indices are reserved
-independently.
-
-To look up the owner of a reserved index use :func:`TSHttpTxnArgIndexNameLookup` or
-:func:`TSHttpSsnArgIndexNameLookup` for transaction and session plugin argument respectively. If
-:arg:`name` is found as an owner, the function returns :code:`TS_SUCCESS` and :arg:`arg_index` is
-updated with the index reserved under that name. If :arg:`description` is not :code:`NULL` then
-the character pointer to which it points will be updated to point at the description for that
-reserved index. This enables communication between plugins where plugin "A" reserves an index under
-a well known name and plugin "B" locates the index by looking it up under that name.
-
-The owner of a reserved index can be found with :func:`TSHttpTxnArgIndexLookup` or
-:func:`TSHttpSsnArgIndexLookup` for transaction and session plugin arguments respectively. If
-:arg:`arg_index` is reserved then the function returns :code:`TS_SUCCESS` and the pointers referred
-to by :arg:`name` and :arg:`description` are updated. :arg:`name` must point at a valid character
-pointer but :arg:`description` can be :code:`NULL` in which case it is ignored.
-
-Manipulating the array is simple. :func:`TSHttpTxnArgSet` sets the array slot at :arg:`arg_idx` for
-the transaction :arg:`txnp` to the value :arg:`arg`. Note this sets the value only for the specific
-transaction. Similarly :func:`TSHttpSsnArgSet` sets the value for a session argument. The values can
-be retrieved with :func:`TSHttpTxnArgGet` for transactions and :func:`TSHttpSsnArgGet` for sessions,
-which return the specified value. Values that have not been set are :code:`NULL`.
-
-.. note:: Session arguments persist for the entire session, which means potentially across all transactions in that session.
-
-.. note:: Following arg index reservations is conventional, it is not enforced.
diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
index 1af3d5b..c377212 100644
--- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
@@ -141,7 +141,6 @@
 :c:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIFETIME`             :ts:cv:`proxy.config.http.negative_revalidating_lifetime`
 :c:enumerator:`TS_CONFIG_HTTP_NORMALIZE_AE`                               :ts:cv:`proxy.config.http.normalize_ae`
 :c:enumerator:`TS_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS`                     :ts:cv:`proxy.config.http.number_of_redirections`
-:c:enumerator:`TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT`             :ts:cv:`proxy.config.http.parent_proxy.connect_attempts_timeout`
 :c:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD`                :ts:cv:`proxy.config.http.parent_proxy.fail_threshold`
 :c:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME`                    :ts:cv:`proxy.config.http.parent_proxy.retry_time`
 :c:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_TOTAL_CONNECT_ATTEMPTS`        :ts:cv:`proxy.config.http.parent_proxy.total_connect_attempts`
@@ -149,7 +148,6 @@
 :c:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH`                :ts:cv:`proxy.config.http.per_server.connection.match`
 :c:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX`                  :ts:cv:`proxy.config.http.per_server.connection.max`
 :c:enumerator:`TS_CONFIG_HTTP_POST_CHECK_CONTENT_LENGTH_ENABLED`          :ts:cv:`proxy.config.http.post.check.content_length.enabled`
-:c:enumerator:`TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT`              :ts:cv:`proxy.config.http.post_connect_attempts_timeout`
 :c:enumerator:`TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY`                :ts:cv:`proxy.config.http.redirect_use_orig_cache_key`
 :c:enumerator:`TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED`                     :ts:cv:`proxy.config.http.request_buffer_enabled`
 :c:enumerator:`TS_CONFIG_HTTP_REQUEST_HEADER_MAX_SIZE`                    :ts:cv:`proxy.config.http.request_header_max_size`
diff --git a/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst b/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst
index afa0658..e5cef85 100644
--- a/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst
@@ -28,7 +28,7 @@
 
     #include <ts/ts.h>
 
-.. c:function:: TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp)
+.. c:function:: TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp, bool *client_abort)
 
 Description
 -----------
@@ -37,6 +37,10 @@
 transaction is aborted. This function should be used to determine whether
 a transaction has been aborted before attempting to cache the results.
 
+Broadly, transaction aborts can be classified into either client side aborts or
+server side. To distinguish between these, we have another boolean parameter
+which gets set to TRUE in case of client side aborts.
+
 Return values
 -------------
 
diff --git a/doc/developer-guide/api/functions/TSRPCRegister.en.rst b/doc/developer-guide/api/functions/TSRPCRegister.en.rst
new file mode 100644
index 0000000..be63393
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSRPCRegister.en.rst
@@ -0,0 +1,256 @@
+.. 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
+
+.. default-domain:: c
+
+TSRPCRegister
+*************
+
+Traffic Server JSONRPC method registration.
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+    #include <ts/ts.h>
+
+.. type:: TSYaml
+
+   An opaque pointer to an internal representation of a YAML::Node type from yamlcpp library..
+
+
+.. type:: TSRPCProviderHandle
+
+   An opaque pointer to an internal representation of rpc::RPCRegistryInfo. This object contains context information about the RPC
+   handler.
+
+.. type:: TSRPCHandlerOptions
+
+   This class holds information about how a handler will be managed and delivered when called. The JSON-RPC manager would use this
+   object to perform certain validation.
+
+
+   .. var:: bool restricted
+
+      Tells the RPC Manager if the call can be delivered or not based on the config rules.
+
+.. type::  void (*TSRPCMethodCb)(const char *id, TSYaml params);
+
+   JSONRPC callback signature for method.
+
+.. type::  void (*TSRPCNotificationCb)(TSYaml params);
+
+   JSONRPC callback signature for notification.
+
+.. function:: TSRPCProviderHandle TSRPCRegister(const char *provider_name, size_t provider_len, const char *yamlcpp_lib_version, size_t yamlcpp_lib_len);
+.. function:: TSReturnCode TSRPCRegisterMethodHandler(const char *name, size_t name_len, TSRPCMethodCb callback, TSRPCProviderHandle info, const TSRPCHandlerOptions *opt);
+.. function:: TSReturnCode TSRPCRegisterNotificationHandler(const char *name, size_t name_len, TSRPCNotificationCb callback, TSRPCProviderHandle info, const TSRPCHandlerOptions *opt);
+.. function:: TSReturnCode TSRPCHandlerDone(TSYaml resp);
+.. function:: void TSRPCHandlerError(int code, const char *descr, size_t descr_len);
+
+Description
+===========
+
+:type:`TSRPCRegister` Should be used to accomplish two basic tasks:
+
+#. To perform a ``yamlcpp`` version library validation.
+    To avoid binary compatibility issues with some plugins using a different ``yamlcpp`` library version, plugins should
+    check-in with TS before registering any handler and validate that their ``yamlcpp`` version is the same as used internally
+    in TS.
+
+#. To create the ``TSRPCProviderHandle`` that will be used as a context object for each subsequent handler registration.
+    The ``provider_name`` will be used as a part of the service descriptor API(:ref:`get_service_descriptor`) which is available as part of the RPC api.
+
+    .. code-block:: cpp
+
+        TSRPCProviderHandle info = TSRPCRegister("FooBar's Plugin!", 16, "0.7.0", 5);
+        ...
+        TSRPCHandlerOptions opt{{true}};
+        TSRPCRegisterMethodHandler("my_join_string_handler", 22, func, info, &opt);
+
+
+    Then when requesting :ref:`get_service_descriptor` It will then display as follows:
+
+    .. code-block:: json
+
+        {
+            "jsonrpc":"2.0",
+            "result":{
+            "methods":[
+                {
+                    "name":"my_join_string_handler",
+                    "type":"method",
+                    "provider":"FooBar's plugin!",
+                    "schema":{ }
+                }
+            ]
+        }
+
+
+.. note::
+
+   We will provide binary compatibility within the lifespan of a major release.
+
+:arg:`provider_name` should be a string with the Plugin's descriptor. A null terminated string is expected.
+
+:arg:`provider_len` should be the length of the provider string..
+
+:arg:`yamlcpp_lib_version` should be a string with the yaml-cpp library version
+the plugin is using. A null terminated string is expected.
+
+:arg:`yamlcpp_lib_len` should be the length of the yamlcpp_lib_len string.
+
+:c:func:`TSRPCRegisterMethodHandler` Add new registered method handler to the JSON RPC engine.
+
+:arg:`name` call name to be exposed by the RPC Engine, this should match the incoming request.
+If you register **get_stats** then the incoming jsonrpc call should have this very
+same name in the **method** field. .. {...'method': 'get_stats'...}.
+
+:arg:`name_len` The length of the name string.
+
+:arg:`callback` The function to be registered. Check :c:func:`TSRPCMethodCb`.
+
+:arg:`info` TSRPCProviderHandle pointer,
+this will be used to provide more context information about this call. It is expected to use the one created by ``TSRPCRegister``.
+
+:arg:`opt` Pointer to `TSRPCHandlerOptions`` object. This will be used to store specifics about a particular call, the rpc
+manager will use this object to perform certain actions. A copy of this object will be stored by the rpc manager.
+
+Please check :ref:`jsonrpc_development` for examples.
+
+:c:func:`TSRPCRegisterNotificationHandler` Add new registered method handler to the JSON RPC engine.
+
+:arg:`name` call name to be exposed by the RPC Engine, this should match the incoming request.
+If you register **get_stats** then the incoming jsonrpc call should have this very
+same name in the **method** field. .. {...'method': 'get_stats'...}.
+
+:arg:`name_len` The length of the name string.
+
+:arg:`callback` The function to be registered. Check :c:func:`TSRPCNotificationCb`.
+
+:arg:`info` TSRPCProviderHandle pointer,
+this will be used to provide more context information about this call. It is expected to use the one created by ``TSRPCRegister``.
+
+:arg:`opt` Pointer to `TSRPCHandlerOptions`` object. This will be used to store specifics about a particular call, the rpc
+manager will use this object to perform certain actions. A copy of this object will be stored by the rpc manager.
+
+Please check :ref:`jsonrpc_development` for examples.
+
+:c:func:`TSRPCHandlerDone` Function to notify the JSONRPC engine that the plugin handler is finished processing the current request.
+This function must be used when implementing a 'method' rpc handler. Once the work is done and the
+response is ready to be sent back to the client, this function should be called.
+Is expected to set the YAML node as response. If the response is empty a **success** message will be
+added to the client's response. The :arg:`resp` holds the *YAML::Node* response for this call.
+
+
+Example:
+
+    .. code-block:: cpp
+
+        void my_join_string_handler(const char *id, TSYaml p) {
+            // id = "abcd-id"
+            // join string "["abcd", "efgh"]
+            std::string join = join_string(p);
+            YAML::Node resp;
+            resp["join"] = join;
+
+            TSRPCHandlerDone(reinterpret_cast<TSYaml>(&resp));
+        }
+
+    This will generate:
+
+    .. code-block:: json
+
+        {
+            "jsonrpc":"2.0",
+            "result":{
+                "join":"abcdefgh"
+            },
+            "id":"abcd-id"
+        }
+
+
+:c:func:`TSRPCHandlerError` Function to notify the JSONRPC engine that the plugin handler is finished processing the current request with an error.
+
+:arg:`code` Should be the error number for this particular error.
+
+:arg:`descr` should be a text with a description of the error. It's up to the
+developer to specify their own error codes and description. Error will be part of the *data* field in the jsonrpc response. See :ref:`jsonrpc-error`
+
+:arg:`descr_len`` The length of the description string.
+
+Example:
+
+    .. code-block:: cpp
+
+        void my_handler_func(const char *id, TSYaml p) {
+            try {
+                // some code
+            } catch (std::exception const &e) {
+                std::string buff;
+                ts::bwprint(buff, "Error during rpc handling: {}.", e.what());
+                TSRPCHandlerError(ID_123456, buff.c_str(), buff.size());
+                return;
+            }
+        }
+
+    This will generate:
+
+    .. code-block:: json
+
+        {
+            "jsonrpc":"2.0",
+            "error":{
+                "code":9,
+                "message":"Error during execution",
+                "data":[
+                    {
+                        "code":123456,
+                        "message":"Error during rpc handling: File not found."
+                    }
+                ]
+            },
+            "id":"abcd-id"
+        }
+
+.. important::
+
+    You must always inform the RPC after processing the jsonrpc request. Either by calling :c:func:`TSRPCHandlerDone` or :c:func:`TSRPCHandlerError`
+    . Calling either of these functions twice is a serious error. You should call exactly one of these functions.
+
+Return Values
+=============
+
+:c:func:`TSRPCRegister` returns :const:`TS_SUCCESS` if all is good, :const:`TS_ERROR` if the :arg:`yamlcpp_lib_version`
+was not set, or the ``yamlcpp`` version does not match with the one used internally in TS.
+
+:c:func:`TSRPCRegisterMethodHandler` :const:`TS_SUCCESS` if the handler was successfully registered, :const:`TS_ERROR` if the handler is already registered.
+
+:c:func:`TSRPCRegisterNotificationHandler`:const:`TS_SUCCESS` if the handler was successfully registered, :const:`TS_ERROR` if the handler is already registered.
+
+:c:func:`TSRPCHandlerDone` Returns :const:`TS_SUCCESS` if no issues, or  :const:`TS_ERROR` if an issue was found.
+
+:c:func:`TSRPCHandlerError` Returns :const:`TS_SUCCESS` if no issues, or  :const:`TS_ERROR` if an issue was found.
+
+
+See also
+========
+
+Please check :ref:`jsonrpc_development` for more details on how to use this API.
+
diff --git a/doc/developer-guide/api/functions/TSRemap.en.rst b/doc/developer-guide/api/functions/TSRemap.en.rst
index b21989a..20b9a55 100644
--- a/doc/developer-guide/api/functions/TSRemap.en.rst
+++ b/doc/developer-guide/api/functions/TSRemap.en.rst
@@ -39,6 +39,7 @@
 .. function:: TSReturnCode TSRemapNewInstance(int argc, char * argv[], void ** ih, char * errbuff, int errbuff_size)
 .. function:: void TSRemapDeleteInstance(void * )
 .. function:: void TSRemapOSResponse(void * ih, TSHttpTxn rh, int os_response_type)
+.. function:: void* TSRemapDLHandleGet(TSRemapPluginInfo plugin_info)
 
 Description
 ===========
@@ -64,6 +65,9 @@
 will call :func:`TSRemapDeleteInstance`. At that point, it's safe to remove
 any data or continuations associated with that instance.
 
+The function :func:`TSRemapDLHandleGet` will return the handle created for
+the loaded plugin, as returned by `dlopen()`.
+
 :func:`TSRemapDoRemap` is called for each HTTP transaction. This is a mandatory
 entry point. In this function, the remap plugin may examine and modify
 the HTTP transaction.
diff --git a/doc/developer-guide/api/functions/TSTypes.en.rst b/doc/developer-guide/api/functions/TSTypes.en.rst
index c2f9fe1..e7a2742 100644
--- a/doc/developer-guide/api/functions/TSTypes.en.rst
+++ b/doc/developer-guide/api/functions/TSTypes.en.rst
@@ -196,6 +196,16 @@
       The API version of the C API. The lower 16 bits are the minor version, and the upper bits
       the major version.
 
+   .. member:: TSRemapPluginInfo plugin_info
+
+      An opaque object, which holds various details about the currently loaded and running
+      plugin. You can't access the details directly, but rather must call an access function
+      such as :func:`TSRemapDLHandleGet`.
+
+.. type:: TSRemapPluginInfo
+
+   Opaque data passed to a remap plugin via :func:`TSRemapInit` in the `plugin_info` member above.
+
 .. type:: TSRemapRequestInfo
 
    Data passed to a remap plugin during the invocation of a remap rule.
diff --git a/doc/developer-guide/api/functions/TSVConnArgs.en.rst b/doc/developer-guide/api/functions/TSVConnArgs.en.rst
deleted file mode 100644
index be57529..0000000
--- a/doc/developer-guide/api/functions/TSVConnArgs.en.rst
+++ /dev/null
@@ -1,88 +0,0 @@
-.. 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
-.. default-domain:: c
-
-TSVConnArgs
-************
-
-Synopsis
-========
-
-.. note::
-
-   This set of API is obsoleted as of ATS v9.0.0, and will be removed with ATS v10.0.0!
-   For details of the new APIs, see :ref:`tsuserargs`.
-
-
-.. code-block:: cpp
-
-    #include <ts/ts.h>
-
-.. function:: TSReturnCode TSVConnArgIndexReserve(const char * name, const char * description, int * arg_idx)
-.. function:: TSReturnCode TSVConnArgIndexNameLookup(const char * name, int * arg_idx, const char ** description)
-.. function:: TSReturnCode TSVConnArgIndexLookup(int arg_idx, const char ** name, const char ** description)
-.. function:: void TSVConnArgSet(TSVConn vc, int arg_idx, void * arg)
-.. function:: void * TSVConnArgGet(TSVConn vc, int arg_idx)
-
-Description
-===========
-
-Virtual connection objects (API type :c:type:`TSVConn`) support an array of :code:`void *` values that
-are controlled entirely by plugins. These are not used in any way by the core. This allows plugins
-to store data associated with a specific virtual connection for later retrieval by the same plugin
-in a different hook or by another plugin. Because the core does not interact with these values any
-cleanup is the responsibility of the plugin.
-
-To avoid collisions between plugins a plugin should first *reserve* an index in the array by calling
-:func:`TSVConnArgIndexReserve` passing it an identifying name, a description, and a pointer to an
-integer which will get the reserved index. The function returns :code:`TS_SUCCESS` if an index was
-reserved, :code:`TS_ERROR` if not (most likely because all of the indices have already been
-reserved). Generally this will be a file or library scope global which is set at plugin
-initialization. Note the reservation is by convention - nothing stops a plugin from interacting with
-a :code:`TSVConn` arg it has not reserved.
-
-To look up the owner of a reserved index use :func:`TSVConnArgIndexNameLookup`. If the :arg:`name` is
-found as an owner, the function returns :code:`TS_SUCCESS` and :arg:`arg_index` is updated with the
-index reserved under that name. If :arg:`description` is not :code:`nullptr` then it will be updated
-with the description for that reserved index. This enables communication between plugins where
-plugin "A" reserves an index under a well known name and plugin "B" locates the index by looking it
-up under that name.
-
-The owner of a reserved index can be found with :func:`TSVConnArgIndexLookup`. If :arg:`arg_index` is
-reserved then the function returns :code:`TS_SUCCESS` and :arg:`name` and :arg:`description` are
-updated. :arg:`name` must point at a valid character pointer but :arg:`description` can be
-:code:`nullptr`.
-
-Manipulating the array is simple. :func:`TSVConnArgSet` sets the array slot at :arg:`arg_idx` for
-the :arg:`vc` to the value :arg:`arg`. Note this sets the value only for the specific
-:c:type:`TSVConn`. The values can be retrieved with :func:`TSVConnArgGet` which returns the
-specified value. Values that have not been set are :code:`nullptr`.
-
-Hooks
-=====
-
-Although these can be used from any hook that has access to a :c:type:`TSVConn` it will generally be
-the case that :func:`TSVConnArgSet` will be used in early intervention hooks and
-:func:`TSVConnArgGet` in session / transaction hooks. Cleanup should be done on the
-:code:`TS_VCONN_CLOSE_HOOK`.
-
-.. rubric:: Appendix
-
-.. note::
-   This is originally from `Issue 2388 <https://github.com/apache/trafficserver/issues/2388>`__. It
-   has been extended based on discussions with Kees and Leif Hedstrom at the ATS summit.
diff --git a/doc/developer-guide/api/types/TSHttpHookID.en.rst b/doc/developer-guide/api/types/TSHttpHookID.en.rst
index a8356a6..90f75c4 100644
--- a/doc/developer-guide/api/types/TSHttpHookID.en.rst
+++ b/doc/developer-guide/api/types/TSHttpHookID.en.rst
@@ -72,6 +72,8 @@
 
    .. c:enumerator:: TS_HTTP_RESPONSE_CLIENT_HOOK
 
+   .. c:enumerator:: TS_HTTP_REQUEST_CLIENT_HOOK
+
    .. c:enumerator:: TS_SSL_FIRST_HOOK
 
    .. c:enumerator:: TS_VCONN_START_HOOK
diff --git a/doc/developer-guide/api/types/TSMgmtTypes.en.rst b/doc/developer-guide/api/types/TSMgmtTypes.en.rst
index 40526f1..8bd72e0 100644
--- a/doc/developer-guide/api/types/TSMgmtTypes.en.rst
+++ b/doc/developer-guide/api/types/TSMgmtTypes.en.rst
@@ -23,6 +23,10 @@
 Synopsis
 ========
 
+.. important::
+
+   This is now deprecated. All new RPC communication is now done by :ref:`jsonrpc-protocol`
+
 Macros used for RPC communications.
 
 Management Signals
diff --git a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
index fe9a919..9b30e52 100644
--- a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
+++ b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
@@ -81,7 +81,6 @@
 .. c:enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT
 .. c:enumerator:: TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT
 .. c:enumerator:: TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME
-.. c:enumerator:: TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD
 .. c:enumerator:: TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS
 .. c:enumerator:: TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT
 .. c:enumerator:: TS_CONFIG_HTTP_RESPONSE_SERVER_STR
diff --git a/doc/developer-guide/api/types/TSRecordUpdateType.en.rst b/doc/developer-guide/api/types/TSRecordUpdateType.en.rst
index af6c1c2..b378aca 100644
--- a/doc/developer-guide/api/types/TSRecordUpdateType.en.rst
+++ b/doc/developer-guide/api/types/TSRecordUpdateType.en.rst
@@ -47,7 +47,7 @@
 
 .. c:member:: TSRecordUpdateType TS_RECORDUPDATE_RESTART_TM
 
-   The value is updated if the :program:`traffic_manager` process is restarted.
+   Deprecated.
 
 Description
 ===========
diff --git a/doc/developer-guide/config-vars.en.rst b/doc/developer-guide/config-vars.en.rst
index e383705..b693396 100644
--- a/doc/developer-guide/config-vars.en.rst
+++ b/doc/developer-guide/config-vars.en.rst
@@ -124,7 +124,7 @@
       The :program:`traffic_server` process must be restarted for a new value to take effect.
 
    ``RECD_RESTART_TM``
-      The :program:`traffic_manager` process must be restarted for a new value to take effect.
+      Deprecated.
 
 required:``RecordRequiredType``
    Effectively a boolean that specifies if the record is required to be present,
diff --git a/doc/developer-guide/core-architecture/index.en.rst b/doc/developer-guide/core-architecture/index.en.rst
index 97f5971..133483b 100644
--- a/doc/developer-guide/core-architecture/index.en.rst
+++ b/doc/developer-guide/core-architecture/index.en.rst
@@ -27,5 +27,4 @@
 
    heap.en
    hostdb.en
-   rpc.en
    url_rewrite_architecture.en.rst
diff --git a/doc/developer-guide/core-architecture/rpc.en.rst b/doc/developer-guide/core-architecture/rpc.en.rst
deleted file mode 100644
index f447b10..0000000
--- a/doc/developer-guide/core-architecture/rpc.en.rst
+++ /dev/null
@@ -1,279 +0,0 @@
-.. 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
-
-.. highlight:: cpp
-.. default-domain:: cpp
-
-.. _developer-doc-rpc:
-
-.. |TServer| replace:: :program:`traffic_server`
-.. |TManager| replace:: :program:`traffic_manager`
-.. |TCtl| replace:: :program:`traffic_ctl`
-.. |LM| replace:: :class:`LocalManager`
-.. |PM| replace:: :class:`ProcessManager`
-
-RPC
-***
-
-========
-Overview
-========
-
-In order for programs in different address spaces (remote clients and |TManager|) to communicate with |TServer|, there is a RPC mechanism in :ts:git:`mgmt/`.
-
-This is a simple serialization style RPC which runs over unix domain sockets. The following sections will detail the runtime structure, serialization mechanisms and how messages are passed from remote clients to |TServer|.
-
-
-=================
-Runtime Structure
-=================
-
-.. uml::
-   :align: center
-
-   hide empty members
-
-   node "traffic_manager"
-   node "traffic_server"
-
-   [traffic_ctl] <-d-> [traffic_manager] : Remote RPC
-   [other remote clients] <-u-> [traffic_manager] : Remote RPC
-   [traffic_manager] <-r-> [traffic_server] : Local RPC
-   [traffic_server] <-r-> [plugin] : Hook
-
-|TManager| opens a unix domain socket to receive commands from remote clients. |TManager| also has a unix domain socket connection with |TServer| to communicate.
-
-===============
-Message Passing
-===============
-
-Sequence diagram for a command sent from |TCtl| to when it is received by a plugin.
-
-.. figure:: ../../uml/images/RPC-sequence-diagram.svg
-
-.. note::
-
-    Currently a fire and forget model. traffic_manager sends out an asynchronous  signal without any acknowledgment. It then proceeds to send a response itself.
-
-=======================
-Remote RPC vs Local RPC
-=======================
-
-The RPC API for remote clients, such as |TCtl|, etc, is different from the RPC API used between |TManager| and |TServer|.
-
-|TManager| acts like a bridge for remote clients to interact with |TServer|. Thus, it is currently impossible to do things like have |TCtl| directly send messages to |TServer|. Classes suffixed with "Remote", ie. :ts:git:`CoreAPIRemote.cc`, and classes suffixed with "Local", ie. :ts:git:`NetworkUtilsLocal.cc` are for remote and local clients, respectively. The following sections will note which set of RPC's are relevant.
-
-=======================
-Serialization Mechanism
-=======================
-
-.. class:: MgmtMarshall
-
-   This is the class used to marshall data objects. It provides functions to marshall and unmarshall data. Each data object is associated with a field. Fields are of :type:`MgmtMarshallType`:
-
-    - **MGMT_MARSHALL_INT** : 4 bytes.
-    - **MGMT_MARSHALL_LONG** : 8 bytes.
-    - **MGMT_MARSHALL_STRING** : 4 bytes to indicate the string size in bytes, followed by the entire string and NULL terminator.
-    - **MGMT_MARSHALL_DATA** : 4 byt es to indicate data size in bytes, followed by the entire data object.
-
-When data is actually sent over a connection it must first be serialized. This is the general serialization mechanism for RPC communication.
-
-Generally, for remote clients sending messages to |TServer|, the message is serialized using remote RPC APIs. The serialized message is sent to |TManager| and |TManager| then simply relays the message to |TServer|. |TServer| eventually unserializes the message.
-
-Details for how |TManager| and |TServer| communicate are documented in the next section.
-
-Marshalling:
-============
-
-   .. function:: ssize_t mgmt_message_marshall(void *buf, size_t remain, const MgmtMarshallType *fields, unsigned count, ...)
-
-      Variable argument wrapper for ``mgmt_message_marshall_v``. Allows for different data types to be marshalled together as long as a field is specified for each data object. Arguments should be references to objects to be marshalled.
-
-   .. function:: ssize_t mgmt_message_marshall_v(void *buf, size_t remain, const MgmtMarshallType *fields, unsigned count, va_list ap)
-
-      This function goes through all the data objects and serializes them together into a buffer. Based on the field, the number of bytes is determined and if there is enough space, it is written into the buffer.
-
-Unmarshalling:
-==============
-
-   .. function:: ssize_t mgmt_message_parse(const void *buf, size_t len, const MgmtMarshallType *fields, unsigned count, ...)
-
-      Variable argument wrapper for ``mgmt_message_parse_v``. Reference to data object to store unmarshalled message needed for variable arguments.
-
-   .. function:: ssize_t mgmt_message_parse_v(const void *buf, size_t len, const MgmtMarshallType *fields, unsigned count, va_list ap)
-
-      This function parses all the serialized. Based on the field, the number of bytes to be read is determined and copied into a ``MgmtMarshallAnyPtr``.
-
-===================
-Local Serialization
-===================
-
-A RPC message is sent as a :class:`MgmtMessageHdr` followed by the serialized data in bytes. Serialization is very simple as the interface for communication between |TManager| and |TServer| only allows for :class:`MgmtMessageHdr`, which is a fixed size, and raw data in the form of :code:`char*` to be sent. A header specifies a :arg:`msg_id` and the :arg:`data_len`. On a read, the header is *always* first read. Based on the length of the data payload, the correct number of bytes is then read from the socket. On a write, the header is first populated and sent on the socket, followed by the raw data.
-
-.. class:: MgmtMessageHdr
-
-   .. member:: int msg_id
-
-      ID for the event or signal to be sent.
-
-   .. member:: int data_len
-
-      Length in bytes of the message.
-
-.. uml::
-
-   |Write|
-   start
-   :populate msg_id;
-   :populate data_len;
-   |#LightGreen|Socket|
-   :send MgmtMessageHdr;
-   |Write|
-   :get the raw data;
-   note left : casts from\nchar* to void*
-   |Socket|
-   :send raw data;
-   |Read|
-   :different address space;
-   : ...\nsome time later;
-   |Socket|
-   :read MgmtMessageHdr;
-   |Read|
-   :get data_len;
-   |Socket|
-   :read data_len bytes;
-   |Read|
-   :choose callback based on msg_id\nand send raw data;
-   stop
-
-==========================
-RPC API for remote clients
-==========================
-
-:ts:git:`NetworkMessage.cc` provides a set of APIs for remote clients to communicate with |TManager|.
-
-|TManager| will then send a response with the return value. The return value only indicates if the request was successfully propagated to |TServer|. Thus, there is no way currently for |TServer| to send information back to remote clients.
-
-.. function:: TSMgmtError send_mgmt_request(const mgmt_message_sender &snd, OpType optype, ...)
-.. function:: TSMgmtError send_mgmt_request(int fd, OpType optype, ...)
-
-      Send a request from a remote client to |TManager|.
-
-.. function:: TSMgmtError send_mgmt_response(int fd, OpType optype, ...)
-
-      Send a response from |TManager| to remote client.
-
-.. function:: TSMgmtError send_mgmt_error(int fd, OpType optype, TSMgmtError error)
-
-      Send err from |TManager| to remote client.
-
-.. function:: TSMgmtError recv_mgmt_request(void *buf, size_t buflen, OpType optype, ...)
-
-      Read request from remote client. Used by |TManager|.
-
-.. function:: TSMgmtError recv_mgmt_response(void *buf, size_t buflen, OpType optype, ...)
-
-      Read response from |TManager|. Used by remote clients.
-
-Using Remote RPC API Example
-============================
-
-This details how a remote client may use the :ts:git:`NetworkMessage.cc` interface to send messages to |TManager|. Leveraging :func:`send_mgmt_request` with a :class:`mgmt_message_sender` which specifies how *send* a message, a remote client can implement its own way to send messages to |TManager| to allow for things such as preprocessing messages.
-
-.. class:: mgmt_message_sender
-
-.. class:: mgmtapi_sender : public mgmt_message_sender
-
-   .. function:: TSMgmtError send(void *msg, size_t msglen) const override
-
-   Specifies how the message is to be sent. Can do things such as serialization or preprocessing based on parameters.
-
-.. code-block:: cpp
-
-   #define MGMTAPI_SEND_MESSAGE(fd, optype, ...) send_mgmt_request(mgmtapi_sender(fd), (optype), __VA_ARGS__)
-
-Now, using this macro, messages can easily be sent. For example:
-
-.. code-block:: cpp
-
-  TSMgmtError ret;
-  MgmtMarshallData reply    = {nullptr, 0};
-
-  // create and send request
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, op, &optype);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  // get a response
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-====================================
-RPC API for |TServer| and |TManager|
-====================================
-
-.. figure:: ../../uml/images/RPC-states.svg
-   :align: center
-
-|LM| and |PM| follow similar workflows. A manager will poll the socket for any messages. If it is able to read a message, it will handle it based on the :arg:`msg_id` from the :class:`MgmtMessageHdr` and select a callback to run asynchoronously. The async callback will add a response, if any, to an outgoing event queue within the class. A manager will continue to poll and read on the socket as long as there are messages available. Two things can stop a manager from polling.
-
-1. there are no longer any messages on the socket for a *timeout* time period.
-
-#. an external event, for example, a |TCtl| command, triggers an ``eventfd`` to tell the manager to stop polling. In the case of an external event, the manager will finish reading anything on the socket, but it will not wait for any timeout period. So, if there are three pending events on the socket, all three will be processed and then immediately after, the manager will stop polling.
-
-Once a manager is done polling, it will begin to send out messages from it's outgoing event queue. It continues to do this until there are no more messages left. Note that as a manager is polling, it is unable to write anything on the socket itself so the number of messages read will always be exhaustive.
-
-.. class:: BaseManager
-
-LocalManager
-============
-
-.. class:: LocalManager :  public BaseManager
-
-   This class is used by |TManager| to communicate with |TServer|
-
-   .. function:: void pollMgmtProcessServer()
-
-      This function watches the socket and handles incoming messages from processes. Used in the main event loop of |TManager|.
-
-   .. function:: void sendMgmtMsgToProcesses(MgmtMessageHdr *mh)
-
-      This function is used by |TManager| to process the messages based on :arg:`msg_id`. It then sends the message to |TServer| over sockets.
-
-ProcessManager
-==============
-
-.. class:: ProcessManager : public BaseManager
-
-   This class is used by |TServer| to communicate with |TManager|
-
-   .. function:: int pollLMConnection()
-
-      This function periodically polls the socket to see if there are any messages from the |LM|.
-
-   .. function:: void signalManager (int msg_id, const char *data_raw, int data_len)
-
-      This function sends messages to the |LM| using sockets.
-
-.. note::
-
-   1. Both :func:`LocalManager::pollMgmtProcessServer` and :func:`ProcessManager::pollLMConnection` actually use ``select``, not ``poll`` or ``epoll``, underneath.
diff --git a/doc/developer-guide/index.en.rst b/doc/developer-guide/index.en.rst
index 471a6fe..c273039 100644
--- a/doc/developer-guide/index.en.rst
+++ b/doc/developer-guide/index.en.rst
@@ -58,3 +58,4 @@
    design-documents/index.en
    layout/index.en
    testing/index.en
+   jsonrpc/index.en
diff --git a/doc/developer-guide/introduction/header-file-structure.en.rst b/doc/developer-guide/introduction/header-file-structure.en.rst
index edee3b1..ae7490c 100644
--- a/doc/developer-guide/introduction/header-file-structure.en.rst
+++ b/doc/developer-guide/introduction/header-file-structure.en.rst
@@ -34,14 +34,14 @@
    same directory). These provide functionality that is used inside the |TS| core logic but has been
    demonstrated to be useful for plugins as well [#]_. The functions are in the library
    ``libtscpputil.so``, although many of the utilities are header only. This library is linked in to
-   the ``traffic_server`` and ``traffic_manager`` binaries and so linkage may not be needed for a plugin.
+   the ``traffic_server`` binary and so linkage may not be needed for a plugin.
 
    This library is independent of the C++ API and can be used with or without that library.
 
 "tscore"
    |TS| core header files. These can only be used inside |TS| itself because they either depend on internal
-   data structures either directly or operationally. This is linked in to the ``traffic_server`` and
-   ``traffic_manager`` binaries therefore has no explicit linkage when used in the core.
+   data structures either directly or operationally. This is linked in to the ``traffic_server`` binary therefore
+   has no explicit linkage when used in the core.
 
 Historical
 ==========
diff --git a/doc/developer-guide/jsonrpc/HandlerError.en.rst b/doc/developer-guide/jsonrpc/HandlerError.en.rst
new file mode 100644
index 0000000..cf78d0e
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/HandlerError.en.rst
@@ -0,0 +1,67 @@
+.. 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
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _jsonrpc-handler-errors:
+
+API Handler error codes
+***********************
+
+High level handler error codes, each particular handler can be fit into one of the following categories.
+A good approach could be the following. This required coordination among all the errors, just for now, this solution seems ok.
+
+.. code-block:: cpp
+
+    enum YourOwnHandlerEnum {
+        FOO_ERROR = Codes::SOME_CATEGORY,
+        ...
+    };
+
+
+.. class:: Codes
+
+   .. enumerator:: CONFIGURATION = 1
+
+      Errors during configuration api handling.
+
+    .. enumerator:: METRIC = 1000
+
+      Errors during metrics api handling.
+
+    .. enumerator:: RECORD = 2000
+
+      Errors during record api handling.
+
+    .. enumerator:: SERVER = 3000
+
+      Errors during server api handling.
+
+    .. enumerator:: STORAGE = 4000
+
+      Errors during storage api handling.
+
+    .. enumerator:: PLUGIN = 4000
+
+      Errors during plugion api handling.
+
+    .. enumerator:: GENERIC = 30000
+
+      Errors during generic api handling, general errors.
diff --git a/doc/developer-guide/jsonrpc/index.en.rst b/doc/developer-guide/jsonrpc/index.en.rst
new file mode 100644
index 0000000..7d06cc7
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/index.en.rst
@@ -0,0 +1,35 @@
+.. 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
+
+.. _developer-guide-jsonrpc:
+
+JSONRPC
+*******
+
+.. toctree::
+   :maxdepth: 2
+
+   jsonrpc-architecture.en
+   jsonrpc-api.en
+   jsonrpc-node.en
+   jsonrpc-node-errors.en
+   jsonrpc-handler-development.en
+   jsonrpc-client-api.en
+   traffic_ctl-development.en
+   HandlerError.en
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
new file mode 100644
index 0000000..75179cf
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
@@ -0,0 +1,1788 @@
+.. 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
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. |RPC| replace:: JSONRPC 2.0
+
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. _JSON: https://www.json.org/json-en.html
+
+.. |str| replace:: ``string``
+.. |arraynum| replace:: ``array[number]``
+.. |arraynumstr| replace:: ``array[number|string]``
+.. |arraystr| replace:: ``array[string]``
+.. |num| replace:: *number*
+.. |strnum| replace:: *string|number*
+.. |object| replace:: *object*
+.. |array| replace:: *array*
+.. |optional| replace:: ``optional``
+.. |method| replace:: ``method``
+.. |notification| replace:: ``notification``
+.. |arrayrecord| replace:: ``array[record]``
+.. |arrayerror| replace:: ``array[errors]``
+
+.. _jsonrpc-api:
+
+API
+***
+
+.. _jsonrpc-api-description:
+
+Description
+===========
+
+|TS| Implements and exposes management calls using a JSONRPC API.  This API is base on the following two things:
+
+* `JSON  <https://www.json.org/json-en.html>`_  format. Lightweight data-interchange format. It is easy for humans to read and write.
+  It is easy for machines to parse and generate. It's basically a  collection of name/value pairs.
+
+* `JSONRPC 2.0 <https://www.jsonrpc.org/specification>`_ protocol. Stateless, light-weight remote procedure call (RPC) protocol.
+  Primarily this specification defines several data structures and the rules around their processing.
+
+
+In order for programs to communicate with |TS|, the server exposes a ``JSONRRPC 2.0`` API where programs can communicate with it.
+
+
+.. _admin-jsonrpc-api:
+
+Administrative API
+==================
+
+This section describes how to interact with the administrative RPC API to interact with |TS|
+
+..
+   _This: We should explain how to deal with permission once it's implemented.
+
+
+
+.. _Records:
+
+Records
+-------
+
+When interacting with the admin api, there are a few structures that need to be understood, this section will describe each of them.
+
+
+.. _RecordRequest:
+
+RPC Record Request
+~~~~~~~~~~~~~~~~~~
+
+To obtain information regarding a particular record(s) from |TS|, we should use the following fields in an *unnamed* json structure.
+
+
+====================== ============= ================================================================================================================
+Field                  Type          Description
+====================== ============= ================================================================================================================
+``record_name``        |str|         The name we want to query from |TS|. This is |optional| if ``record_name_regex`` is used.
+``record_name_regex``  |str|         The regular expression we want to query from |TS|. This is |optional| if ``record_name`` is used.
+``rec_types``          |arraynumstr| |optional| A list of types that should be used to match against the found record. These types refer to ``RecT``.
+                                       Other values (in decimal) than the ones defined by the ``RecT`` ``enum`` will be ignored. If no type is
+                                       specified, the server will not match the type against the found record.
+====================== ============= ================================================================================================================
+
+.. note::
+
+   If ``record_name`` and ``record_name_regex`` are both provided, the server will not use any of them. Only one should be provided.
+
+
+Example:
+
+   #. Single record:
+
+   .. code-block:: json
+
+      {
+         "id":"2947819a-8563-4f21-ba45-bde73210e387",
+         "jsonrpc":"2.0",
+         "method":"admin_lookup_records",
+         "params":[
+            {
+               "record_name":"proxy.config.exec_thread.autoconfig.scale",
+               "rec_types":[
+                  1,
+                  16
+               ]
+            }
+         ]
+      }
+
+   #. Multiple records:
+
+   .. code-block:: json
+      :emphasize-lines: 5-12
+
+      {
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_lookup_records",
+         "params": [{
+               "record_name": "proxy.config.exec_thread.autoconfig.scale"
+            },
+            {
+               "record_name": "proxy.config.log.rolling_interval_sec",
+               "rec_types": [1]
+            }
+         ]
+      }
+
+   #. Batch Request
+
+   .. code-block:: json
+
+      [
+         {
+            "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+            "jsonrpc": "2.0",
+            "method": "admin_lookup_records",
+            "params": [{
+               "record_name_regex": "proxy.config.exec_thread.autoconfig.scale",
+               "rec_types": [1]
+            }]
+         }, {
+            "id": "dam7018e-0720-11eb-abe2-001fc69dd123",
+            "jsonrpc": "2.0",
+            "method": "admin_lookup_records",
+            "params": [{
+               "record_name_regex": "proxy.config.log.rolling_interval_sec",
+               "rec_types": [1]
+            }]
+         }
+      ]
+
+
+.. _RecordResponse:
+
+
+RPC Record Response
+~~~~~~~~~~~~~~~~~~~
+
+When querying for a record(s), in the majority of the cases the record api will respond with the following json structure.
+
+=================== ==================== ========================================================================
+Field               Type                 Description
+=================== ==================== ========================================================================
+``recordList``      |arrayrecord|         A list of record |object|. See `RecordRequestObject`_
+``errorList``       |arrayerror|          A list of error |object|. See `RecordErrorObject`_
+=================== ==================== ========================================================================
+
+
+.. _RecordErrorObject:
+
+RPC Record Error Object
+~~~~~~~~~~~~~~~~~~~~~~~
+
+All errors that are found during a record query, will be returned back to the caller in the ``error_list`` field as part of the `RecordResponse`_ object.
+The record errors have the following fields.
+
+
+=================== ============= ===========================================================================
+Field               Type          Description
+=================== ============= ===========================================================================
+``code``            |str|         |optional| An error code that should be used to get a description of the error.(Add error codes)
+``record_name``     |str|         |optional| The associated record name, this may be omitted sometimes.
+``message``         |str|         |optional| A descriptive message. The server can omit this value.
+=================== ============= ===========================================================================
+
+
+Example:
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "code": "2007",
+         "record_name": "proxy.config.exec_thread.autoconfig.scale"
+      }
+
+
+Examples:
+
+#. Request a non existing record among with an invalid type for a record:
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_lookup_records",
+         "params": [
+            {
+                  "record_name": "non.existing.record"
+            },
+            {
+                  "record_name": "proxy.process.http.total_client_connections_ipv4",
+                  "rec_types": [1]
+            }
+         ]
+      }
+
+   Line ``7`` requests a non existing record and in line ``11`` we request a type that does not match the record's type.
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "errorList":[
+               {
+                  "code":"2000",
+                  "record_name":"non.existing.record"
+               },
+               {
+                  "code":"2007",
+                  "record_name":"proxy.process.http.total_client_connections_ipv4"
+               }
+            ]
+         },
+         "id":"ded7018e-0720-11eb-abe2-001fc69cc946"
+      }
+
+   In this case we get the response indicating that the requested fields couldn't be retrieved. See `RecordErrorObject`_ for more details.
+
+   The error list can also be included among with a recordList
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[]
+            ,"errorList":[
+               {
+                  "code":"2000",
+                  "record_name":"non.existing.record"
+               },
+               {
+                  "code":"2007",
+                  "record_name":"proxy.process.http.total_client_connections_ipv4"
+               }
+            ]
+         },
+         "id":"ded7018e-0720-11eb-abe2-001fc69cc946"
+      }
+
+   .. note::
+
+      If there is no errors to report, the "errorList" field will be represented as an empty  list (``[]``).
+
+.. _RecordErrorObject-Enum:
+
+
+JSONRPC Record Errors
+~~~~~~~~~~~~~~~~~~~~~
+
+The following errors could be generated when requesting record from the server.
+
+.. class:: RecordError
+
+   .. enumerator:: RECORD_NOT_FOUND = 2000
+
+      Record not found.
+
+   .. enumerator:: RECORD_NOT_CONFIG = 2001
+
+      Record is not a configuration type.
+
+   .. enumerator:: RECORD_NOT_METRIC = 2002
+
+      Record is not a metric type.
+
+   .. enumerator:: INVALID_RECORD_NAME = 2003
+
+      Invalid Record Name.
+
+   .. enumerator:: VALIDITY_CHECK_ERROR = 2004
+
+      Validity check failed.
+
+   .. enumerator:: GENERAL_ERROR = 2005
+
+      Error reading the record.
+
+   .. enumerator:: RECORD_WRITE_ERROR = 2006
+
+      Generic error while writting the record. ie: RecResetStatRecord() returns  REC_ERR_OKAY
+
+   .. enumerator:: REQUESTED_TYPE_MISMATCH = 2007
+
+      The requested record's type does not match againts the passed type list.
+
+   .. enumerator:: INVALID_INCOMING_DATA = 2008
+
+      This could be caused by an invalid value in the incoming request which may cause the parser to fail.
+
+
+.. _RecordRequestObject:
+
+RPC Record Object
+~~~~~~~~~~~~~~~~~
+
+This is mapped from a ``RecRecord``, when requesting for a record the following information will be populated into a json |object|.
+The ``record`` structure has the following members.
+
+=================== ======== ==================================================================
+Record Field        Type     Description
+=================== ======== ==================================================================
+``current_value``   |str|    Current value that is held by the record.
+``default_value``   |str|    Record's default value.
+``name``            |str|    Record's name
+``order``           |str|    Record's order
+``overridable``     |str|    Records's overridable configuration.
+``raw_stat_block``  |str|    Raw Stat Block Id.
+``record_class``    |str|    Record type. Mapped from ``RecT``
+``record_type``     |str|    Record's data type. Mapped from RecDataT
+``version``         |str|    Record's version.
+``stats_meta``      |object| Stats metadata `stats_meta`_
+``config_meta``     |object| Config metadata `config_meta`_
+=================== ======== ==================================================================
+
+* it will be either ``config_meta`` or ``stats_meta`` object, but never both*
+
+
+.. _config_meta:
+
+Config Metadata
+
+=================== ======== ==================================================================
+Record Field        Type     Description
+=================== ======== ==================================================================
+`access_type`       |str|    Access type. This is mapped from ``TSRecordAccessType``.
+`check_expr`        |str|    Syntax checks regular expressions.
+`checktype`         |str|    Check type, This is mapped from ``RecCheckT``.
+`source`            |str|    Source of the configuration value. Mapped from RecSourceT
+`update_status`     |str|    Update status flag.
+`update_type`       |str|    How the records get updated. Mapped from RecUpdateT
+=================== ======== ==================================================================
+
+
+.. _stats_meta:
+
+Stats Metadata (TBC)
+
+=================== ======== ==================================================================
+Record Field        Type     Description
+=================== ======== ==================================================================
+`persist_type`      |str|    Persistent type. This is mapped from ``RecPersistT``
+=================== ======== ==================================================================
+
+
+Example with config meta:
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "record":{
+            "record_name":"proxy.config.diags.debug.tags",
+            "record_type":"3",
+            "version":"0",
+            "raw_stat_block":"0",
+            "order":"421",
+            "config_meta":{
+               "access_type":"0",
+               "update_status":"0",
+               "update_type":"1",
+               "checktype":"0",
+               "source":"3",
+               "check_expr":"null"
+            },
+            "record_class":"1",
+            "overridable":"false",
+            "data_type":"STRING",
+            "current_value":"rpc",
+            "default_value":"http|dns"
+         }
+      }
+
+Example with stats meta:
+
+   .. code-block:: json
+      :linenos:
+
+         {
+            "record": {
+               "current_value": "0",
+               "data_type": "COUNTER",
+               "default_value": "0",
+               "order": "8",
+               "overridable": "false",
+               "raw_stat_block": "10",
+               "record_class": "2",
+               "record_name": "proxy.process.http.total_client_connections_ipv6",
+               "record_type": "4",
+               "stat_meta": {
+                  "persist_type": "1"
+               },
+               "version": "0"
+            }
+         }
+
+.. _jsonrpc-admin-api:
+
+JSONRPC API
+===========
+
+* `admin_lookup_records`_
+
+* `admin_clear_all_metrics_records`_
+
+* `admin_config_set_records`_
+
+* `admin_config_reload`_
+
+* `admin_clear_metrics_records`_
+
+* `admin_clear_all_metrics_records`_
+
+* `admin_host_set_status`_
+
+* `admin_server_stop_drain`_
+
+* `admin_server_start_drain`_
+
+* `admin_plugin_send_basic_msg`_
+
+* `admin_storage_get_device_status`_
+
+* `admin_storage_set_device_offline`_
+
+* `show_registered_handlers`_
+
+* `get_service_descriptor`_
+
+* `filemanager.get_files_registry`_
+
+.. _jsonapi-management-records:
+
+
+Records
+=======
+
+.. _admin_lookup_records:
+
+
+admin_lookup_records
+--------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Obtain  record(s) from TS.
+
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: A list of `RecordRequest`_ objects.
+
+
+Result
+~~~~~~
+
+A list of `RecordResponse`_ . In case of any error obtaining the requested record, the `RecordErrorObject`_ |object| will be included.
+
+
+Examples
+~~~~~~~~
+
+#. Request a configuration record, no errors:
+
+   .. code-block:: json
+
+      {
+         "id":"b2bb16a5-135a-4c84-b0a7-8d31ebd82542",
+         "jsonrpc":"2.0",
+         "method":"admin_lookup_records",
+         "params":[
+            {
+               "record_name":"proxy.config.log.rolling_interval_sec",
+               "rec_types":[
+                  "1",
+                  "16"
+               ]
+            }
+         ]
+      }
+
+Response:
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[
+               {
+                  "record":{
+                     "record_name":"proxy.config.log.rolling_interval_sec",
+                     "record_type":"1",
+                     "version":"0",
+                     "raw_stat_block":"0",
+                     "order":"410",
+                     "config_meta":{
+                        "access_type":"0",
+                        "update_status":"0",
+                        "update_type":"1",
+                        "checktype":"1",
+                        "source":"3",
+                        "check_expr":"^[0-9]+$"
+                     },
+                     "record_class":"1",
+                     "overridable":"false",
+                     "data_type":"INT",
+                     "current_value":"86400",
+                     "default_value":"86400"
+                  }
+               }
+            ]
+            ,"errorList":[]
+         },
+         "id":"b2bb16a5-135a-4c84-b0a7-8d31ebd82542"
+      }
+
+
+#. Request a configuration record, some errors coming back:
+
+   .. code-block:: json
+
+      {
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_lookup_records",
+         "params": [
+            {
+               "rec_types": [1],
+               "record_name": "proxy.config.log.rolling_interval_sec"
+            },
+            {
+               "record_name": "proxy.config.log.rolling_interv"
+            }
+         ]
+      }
+
+
+Response:
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[
+               {
+                  "record":{
+                     "record_name":"proxy.config.log.rolling_interval_sec",
+                     "record_type":"1",
+                     "version":"0",
+                     "raw_stat_block":"0",
+                     "order":"410",
+                     "config_meta":{
+                        "access_type":"0",
+                        "update_status":"0",
+                        "update_type":"1",
+                        "checktype":"1",
+                        "source":"3",
+                        "check_expr":"^[0-9]+$"
+                     },
+                     "record_class":"1",
+                     "overridable":"false",
+                     "data_type":"INT",
+                     "current_value":"86400",
+                     "default_value":"86400"
+                  }
+               }
+            ],
+            "errorList":[
+               {
+                  "code":"2000",
+                  "record_name":"proxy.config.log.rolling_interv"
+               }
+            ]
+         },
+         "id":"ded7018e-0720-11eb-abe2-001fc69cc946"
+      }
+
+
+Request using a `regex` instead of the full name.
+
+.. note::
+
+   Regex lookups use ``record_name_regex` and not ``record_name``. Check `RecordRequestObject`_ .
+
+Examples
+~~~~~~~~
+
+#. Request a mix(config and stats) of records record using a regex, no errors:
+
+   .. code-block:: json
+
+      {
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_lookup_records",
+         "params": [
+            {
+                  "rec_types": [1],
+                  "record_name_regex": "proxy.config.exec_thread.autoconfig.sca*"
+            },
+            {
+                  "rec_types": [2],
+                  "record_name_regex": "proxy.process.http.total_client_connections_ipv"
+            }
+         ]
+      }
+
+
+   Response:
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[
+               {
+                  "record":{
+                     "record_name":"proxy.config.exec_thread.autoconfig.scale",
+                     "record_type":"2",
+                     "version":"0",
+                     "raw_stat_block":"0",
+                     "order":"355",
+                     "config_meta":{
+                        "access_type":"2",
+                        "update_status":"0",
+                        "update_type":"2",
+                        "checktype":"0",
+                        "source":"3",
+                        "check_expr":"null"
+                     },
+                     "record_class":"1",
+                     "overridable":"false",
+                     "data_type":"FLOAT",
+                     "current_value":"1",
+                     "default_value":"1"
+                  }
+               },
+               {
+                  "record":{
+                     "record_name":"proxy.process.http.total_client_connections_ipv4",
+                     "record_type":"4",
+                     "version":"0",
+                     "raw_stat_block":"9",
+                     "order":"7",
+                     "stat_meta":{
+                        "persist_type":"1"
+                     },
+                     "record_class":"2",
+                     "overridable":"false",
+                     "data_type":"COUNTER",
+                     "current_value":"0",
+                     "default_value":"0"
+                  }
+               },
+               {
+                  "record":{
+                     "record_name":"proxy.process.http.total_client_connections_ipv6",
+                     "record_type":"4",
+                     "version":"0",
+                     "raw_stat_block":"10",
+                     "order":"8",
+                     "stat_meta":{
+                        "persist_type":"1"
+                     },
+                     "record_class":"2",
+                     "overridable":"false",
+                     "data_type":"COUNTER",
+                     "current_value":"0",
+                     "default_value":"0"
+                  }
+               }
+            ]
+            ,"errorList":[]
+         },
+         "id":"ded7018e-0720-11eb-abe2-001fc69cc946"
+      }
+
+
+
+#. Request a configuration record using a regex with some errors coming back:
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_lookup_records",
+         "params": [
+            {
+                  "rec_types": [1],
+                  "record_name_regex": "proxy.config.exec_thread.autoconfig.sca*"
+            },
+            {
+                  "rec_types": [987],
+                  "record_name_regex": "proxy.process.http.total_client_connections_ipv"
+            }
+         ]
+      }
+
+
+   Note the invalid ``rec_type`` at line ``11``
+
+   Response:
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[
+               {
+                  "record":{
+                     "record_name":"proxy.config.exec_thread.autoconfig.scale",
+                     "record_type":"2",
+                     "version":"0",
+                     "raw_stat_block":"0",
+                     "order":"355",
+                     "config_meta":{
+                        "access_type":"2",
+                        "update_status":"0",
+                        "update_type":"2",
+                        "checktype":"0",
+                        "source":"3",
+                        "check_expr":"null"
+                     },
+                     "record_class":"1",
+                     "overridable":"false",
+                     "data_type":"FLOAT",
+                     "current_value":"1",
+                     "default_value":"1"
+                  }
+               }
+            ],
+            "errorList":[
+               {
+                  "code":"2008",
+                  "message":"Invalid request data provided"
+               }
+            ]
+         },
+         "id":"ded7018e-0720-11eb-abe2-001fc69cc946"
+      }
+
+
+
+   We get a valid record that was found based on the passed criteria, ``proxy.config.exec_thread.autoconfig.sca*`` and the ``rec_type`` *1*.
+   Also we get a particular error that was caused by the invalid rec types ``987``
+
+
+#. Request all config records
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_lookup_records",
+         "params": [{
+
+            "record_name_regex": ".*",
+            "rec_types": [1, 16]
+
+         }]
+
+      }
+
+
+
+   *Note the `.*` regex we use to match them all. `rec_types` refer to ``RecT` , which in this case we are interested in `CONFIG`
+   records and `LOCAL` records.*
+
+
+   Response:
+
+   All the configuration records. See `RecordResponse`_. The JSONRPC record handler is not limiting the response size.
+
+
+.. note::
+
+   It will retrieve ALL the configuration records, keep in mind that it might be a large response.
+
+
+
+.. _admin_config_set_records:
+
+admin_config_set_records
+------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Set a value for a particular record.
+
+
+
+Parameters
+~~~~~~~~~~
+
+=================== ============= ================================================================================================================
+Field               Type          Description
+=================== ============= ================================================================================================================
+``record_name``     |str|         The name of the record that wants to be updated.
+``new_value``       |str|         The associated record value. Use always a |str| as the internal library will translate to the appropriate type.
+=================== ============= ================================================================================================================
+
+
+Example:
+
+   .. code-block:: json
+
+      [
+        {
+            "record_name": "proxy.config.exec_thread.autoconfig.scale",
+            "record_value": "1.5"
+        }
+      ]
+
+
+Result
+~~~~~~
+
+A list of updated record names. :ref:`RecordErrorObject-Enum` will be included.
+
+Examples
+~~~~~~~~
+
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "a32de1da-08be-11eb-9e1e-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_config_set_records",
+      "params": [
+         {
+               "record_name": "proxy.config.exec_thread.autoconfig.scale",
+               "record_value": "1.3"
+         }
+      ]
+   }
+
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc":"2.0",
+      "result":[
+         {
+            "record_name":"proxy.config.exec_thread.autoconfig.scale"
+         }
+      ],
+      "id":"a32de1da-08be-11eb-9e1e-001fc69cc946"
+   }
+
+.. _admin_config_reload:
+
+admin_config_reload
+-------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Instruct |TS| to start the reloading process. You can find more information about config reload here(add link TBC)
+
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: Omitted
+
+.. note::
+
+   There is no need to add any parameters here.
+
+Result
+~~~~~~
+
+A |str| with the success message indicating that the command was acknowledged by the server.
+
+Examples
+~~~~~~~~
+
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "89fc5aea-0740-11eb-82c0-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_config_reload"
+   }
+
+
+Response:
+
+The response will contain the default `success_response`  or a proper rpc error, check :ref:`jsonrpc-node-errors` for mode details.
+
+
+Validation:
+
+You can request for the record `proxy.node.config.reconfigure_time` which will be updated with the time of the requested update.
+
+
+.. _jsonrpc-api-management-metrics:
+
+Metrics
+=======
+
+.. _admin_clear_metrics_records:
+
+admin_clear_metrics_records
+---------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Clear one or more metric values. This API will take the incoming metric names and reset their associated value. The format for the incoming
+request should follow the  `RecordRequest`_ .
+
+
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: A list of `RecordRequest`_ objects.
+
+.. note::
+
+   Only the ``rec_name`` will be used, if this is not provided, the API will report it back as part of the `RecordErrorObject`_ .
+
+
+Result
+~~~~~~
+
+This api will only inform for errors during the metric update, all errors will be inside the  `RecordErrorObject`_ object.
+Successfully metric updates will not report back to the client. So it can be assumed that the records were properly updated.
+
+.. note::
+
+   As per our internal API if the metric could not be updated because there is no change in the value, ie: it's already ``0`` this will be reported back to the client as part of the  `RecordErrorObject`_
+
+Examples
+~~~~~~~~
+
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_clear_metrics_records",
+      "params": [
+            {
+               "record_name": "proxy.process.http.total_client_connections_ipv6"
+            },
+            {
+               "record_name": "proxy.config.log.rolling_intervi_should_fail"
+            }
+      ]
+   }
+
+
+Response:
+
+.. code-block:: json
+
+   {
+      "jsonrpc": "2.0",
+      "result": {
+         "errorList": [{
+            "code": "2006",
+            "record_name": "proxy.config.log.rolling_intervi_should_fail"
+         }]
+      },
+      "id": "ded7018e-0720-11eb-abe2-001fc69cc946"
+   }
+
+
+.. _admin_clear_all_metrics_records:
+
+admin_clear_all_metrics_records
+-------------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Clear all the metrics.
+
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: This can be Omitted
+
+
+Result
+~~~~~~
+
+This api will only inform for errors during the metric update. Errors will be tracked down in the `error` field.
+
+.. note::
+
+   As per our internal API if the metric could not be updated because there is no change in the value, ie: it's already ``0`` this
+   will be reported back to the client as part of the  `RecordErrorObject`_
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "dod7018e-0720-11eb-abe2-001fc69cc997",
+      "jsonrpc": "2.0",
+      "method": "admin_clear_all_metrics_records"
+   }
+
+
+
+Response:
+
+The response will contain the default `success_response`  or an error. :ref:`jsonrpc-node-errors`.
+
+
+.. _admin_host_set_status:
+
+admin_host_set_status
+---------------------
+
+Description
+~~~~~~~~~~~
+
+A record to track status is created for each host. The name is the host fqdn.
+This record contains the overall status and the status for each reason.
+The records may be viewed using the `admin_host_get_status` rpc api.
+
+Parameters
+~~~~~~~~~~
+
+
+=================== ============= =================================================================================================
+Field               Type          Description
+=================== ============= =================================================================================================
+``operation``       |str|         The name of the record that is meant to be updated.
+``host``            |arraystr|    A list of hosts that we want to interact with.
+``reason``          |str|         Reason for the operation.
+``time``            |str|         Set the duration of an operation to ``count`` seconds. A value of ``0`` means no duration, the
+                                  condition persists until explicitly changed. The default is ``0`` if an operation requires a time
+                                  and none is provided by this option. optional when ``op=up``
+=================== ============= =================================================================================================
+
+operation:
+
+=================== ============= =================================================================================================
+Field               Type          Description
+=================== ============= =================================================================================================
+``up``              |str|         Marks the listed hosts as ``up`` so that they will be available for use as a next hop parent. Use
+                                  ``reason`` to mark the host reason code. The 'self_detect' is an internal reason code
+                                  used by parent selection to mark down a parent when it is identified as itself and
+``down``            |str|         Marks the listed hosts as down so that they will not be chosen as a next hop parent. If
+                                  ``time`` is included the host is marked down for the specified number of seconds after
+                                  which the host will automatically be marked up. A host is not marked up until all reason codes
+                                  are cleared by marking up the host for the specified reason code.
+=================== ============= =================================================================================================
+
+reason:
+
+=================== ============= =================================================================================================
+Field               Type          Description
+=================== ============= =================================================================================================
+``active``          |str|         Set the active health check reason.
+``local``           |str|         Set the local health check reason.
+``manual``          |str|         Set the administrative reason. This is the default reason if a reason is needed and not provided
+                                  by this option. If an invalid reason is provided ``manual`` will be defaulted.
+=================== ============= =================================================================================================
+
+Internally the reason can be ``self_detect`` if
+:ts:cv:`proxy.config.http.parent_proxy.self_detect` is set to the value 2 (the default). This is
+used to prevent parent selection from creating a loop by selecting itself as the upstream by
+marking this reason as "down" in that case.
+
+.. note::
+
+   The up / down status values are independent, and a host is consider available if and only if
+   all of the statuses are "up".
+
+
+Result
+~~~~~~
+
+The response will contain the default `success_response`  or an error. :ref:`jsonrpc-node-errors`.
+
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "c6d56fba-0cbd-11eb-926d-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_host_set_status",
+      "params": {
+         "operation": "up",
+         "host": ["host1"],
+         "reason": "manual",
+         "time": "100"
+      }
+   }
+
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc": "2.0",
+      "result": "success",
+      "id": "c6d56fba-0cbd-11eb-926d-001fc69cc946"
+   }
+
+
+
+Getting the host status
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Get the current status of the specified hosts with respect to their use as targets for parent selection. This returns the serialized
+information for the host.
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_host_get_status",
+      "params": [
+            "host1.mycdn.net"
+       ]
+   }
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc": "2.0",
+      "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+      "result": {
+         "statusList": [{
+            "hostname": "host1.mycdn.net",
+            "status": "HOST_STATUS_DOWN,ACTIVE:UP:0:0,LOCAL:UP:0:0,MANUAL:UP:0:0,SELF_DETECT:DOWN:1646248306"
+            }
+         ]
+         ,"errorList":[]
+      }
+   }
+
+
+.. _admin_server_stop_drain:
+
+admin_server_stop_drain
+-----------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Stop the drain requests process. Recover server from the drain mode
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: Omitted
+
+Result
+~~~~~~
+
+The response will contain the default `success_response`  or an error. :ref:`jsonrpc-node-errors`.
+
+
+Examples
+~~~~~~~~
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "35f0b246-0cc4-11eb-9a79-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_server_stop_drain"
+   }
+
+
+
+.. _admin_server_start_drain:
+
+admin_server_start_drain
+------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Drain TS requests.
+
+Parameters
+~~~~~~~~~~
+
+======================= ============= ================================================================================================================
+Field                   Type          Description
+======================= ============= ================================================================================================================
+``no_new_connections``  |str|         Wait for new connections down to threshold before starting draining, ``yes|true|1``. Not yet supported
+======================= ============= ================================================================================================================
+
+
+Result
+~~~~~~
+
+The response will contain the default `success_response`  or an error. :ref:`jsonrpc-node-errors`.
+
+.. note::
+
+   If the Server is already running a proper error will be sent back to the client.
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "30700808-0cc4-11eb-b811-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_server_start_drain",
+      "params": {
+         "no_new_connections": "yes"
+      }
+   }
+
+
+Response could be either:
+
+#. The response will contain the default `success_response`
+
+#. Response from a server that is already in drain mode.
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc": "2.0",
+      "id": "30700808-0cc4-11eb-b811-001fc69cc946",
+      "error": {
+
+         "code": 9,
+         "message": "Error during execution",
+         "data": [{
+
+            "code": 3000,
+            "message": "Server already draining."
+            }]
+
+      }
+
+   }
+
+
+.. _admin_plugin_send_basic_msg:
+
+admin_plugin_send_basic_msg
+---------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Interact with plugins. Send a message to plugins. All plugins that have hooked the ``TSLifecycleHookID::TS_LIFECYCLE_MSG_HOOK`` will receive a callback for that hook.
+The :arg:`tag` and :arg:`data` will be available to the plugin hook processing. It is expected that plugins will use :arg:`tag` to select relevant messages and determine the format of the :arg:`data`.
+
+Parameters
+~~~~~~~~~~
+
+======================= ============= ================================================================================================================
+Field                   Type          Description
+======================= ============= ================================================================================================================
+``tag``                 |str|         A tag name that will be read by the interested plugin
+``data``                |str|         Data to be send, this is |optional|
+======================= ============= ================================================================================================================
+
+
+Result
+~~~~~~
+
+The response will contain the default `success_response`  or an error. :ref:`jsonrpc-node-errors`.
+
+Examples
+~~~~~~~~
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "id": "19095bf2-0d3b-11eb-b41a-001fc69cc946",
+         "jsonrpc": "2.0",
+         "method": "admin_plugin_send_basic_msg",
+         "params": {
+            "data": "ping",
+            "tag": "pong"
+         }
+      }
+
+
+
+
+.. _admin_storage_get_device_status:
+
+admin_storage_get_device_status
+-------------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Show the storage configuration.
+
+Parameters
+~~~~~~~~~~
+
+A list of |str| names for the specific storage we want to interact with. The storage identification used in the param list should match
+exactly a path  specified in :file:`storage.config`.
+
+Result
+~~~~~~
+
+cachedisk
+
+======================= ============= =============================================================================================
+Field                   Type          Description
+======================= ============= =============================================================================================
+``path``                |str|         Storage identification.  The storage is identified by :arg:`path` which must match exactly a
+                                       path specified in :file:`storage.config`.
+``status``              |str|         Disk status. ``online`` or ``offline``
+``error_count``         |str|         Number of errors on the particular disk.
+======================= ============= =============================================================================================
+
+
+Examples
+~~~~~~~~
+
+Request:
+
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "8574edba-0d40-11eb-b2fb-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_storage_get_device_status",
+      "params": ["/some/path/to/ats/trafficserver/cache.db", "/some/path/to/ats/var/to_remove/cache.db"]
+   }
+
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc": "2.0",
+      "result": [{
+            "cachedisk": {
+               "path": "/some/path/to/ats/trafficserver/cache.db",
+               "status": "online",
+               "error_count": "0"
+            }
+         },
+         {
+            "cachedisk": {
+               "path": "/some/path/to/ats/var/to_remove/cache.db",
+               "status": "online",
+               "error_count": "0"
+            }
+         }
+      ],
+      "id": "8574edba-0d40-11eb-b2fb-001fc69cc946"
+   }
+
+
+
+.. _admin_storage_set_device_offline:
+
+admin_storage_set_device_offline
+--------------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Mark a cache storage device as ``offline``. The storage is identified by :arg:`path` which must match exactly a path specified in
+:file:`storage.config`. This removes the storage from the cache and redirects requests that would have used this storage to
+other storage. This has exactly the same effect as a disk failure for that storage. This does not persist across restarts of the
+:program:`traffic_server` process.
+
+Parameters
+~~~~~~~~~~
+
+A list of |str| names for the specific storage we want to interact with. The storage identification used in the param list should match
+exactly a path  specified in :file:`storage.config`.
+
+Result
+~~~~~~
+
+A list of |object| which the following fields:
+
+
+=========================== ============= =============================================================================================
+Field                       Type          Description
+=========================== ============= =============================================================================================
+``path``                    |str|         Storage identification.  The storage is identified by :arg:`path` which must match exactly a
+                                          path specified in :file:`storage.config`.
+``has_online_storage_left`` |str|         A flag indicating if there is any online storage left after this operation.
+=========================== ============= =============================================================================================
+
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "53dd8002-0d43-11eb-be00-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "admin_storage_set_device_offline",
+      "params": ["/some/path/to/ats/var/to_remove/cache.db"]
+   }
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc": "2.0",
+      "result": [{
+         "path": "/some/path/to/ats/var/to_remove/cache.db",
+         "has_online_storage_left": "true"
+      }],
+      "id": "53dd8002-0d43-11eb-be00-001fc69cc946"
+   }
+
+
+.. _show_registered_handlers:
+
+show_registered_handlers
+------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+List all the registered RPC public handlers.
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: Omitted
+
+Result
+~~~~~~
+
+An |object| with the following fields:
+
+
+================== ============= ===========================================
+Field              Type          Description
+================== ============= ===========================================
+``methods``        |str|         A list of exposed method handler names.
+``notifications``  |str|         A list of exposed notification handler names.
+================== ============= ===========================================
+
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "f4477ac4-0d44-11eb-958d-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "show_registered_handlers"
+   }
+
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "f4477ac4-0d44-11eb-958d-001fc69cc946",
+      "jsonrpc": "2.0",
+      "result": {
+         "methods": [
+               "admin_host_set_status",
+               "admin_server_stop_drain",
+               "admin_server_start_drain",
+               "admin_clear_metrics_records",
+               "admin_clear_all_metrics_records",
+               "admin_plugin_send_basic_msg",
+               "admin_lookup_records",
+               "admin_config_set_records",
+               "admin_storage_get_device_status",
+               "admin_storage_set_device_offline",
+               "admin_config_reload",
+               "show_registered_handlers"
+         ],
+         "notifications": []
+      }
+   }
+
+.. _get_service_descriptor:
+
+get_service_descriptor
+------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+List and describe all the registered RPC handler.
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: Omitted
+
+Result
+~~~~~~
+
+An |object| with the following fields:
+
+
+``methods`` object
+
+=============== ============= ===========================================
+Field           Type          Description
+=============== ============= ===========================================
+``name``        |str|         Handler's name. Call name
+``type``        |str|         Either 'method' or 'notification'
+``provider``    |str|         Provider's information.
+``schema``      |str|         A json-schema definition
+=============== ============= ===========================================
+
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id": "f4477ac4-0d44-11eb-958d-001fc69cc946",
+      "jsonrpc": "2.0",
+      "method": "get_service_descriptor"
+   }
+
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+   "jsonrpc":"2.0",
+   "result":{
+      "methods":[
+         {
+            "name":"admin_host_set_status",
+            "type":"method",
+            "provider":"Traffic Server JSONRPC 2.0 API",
+            "schema":{
+            }
+         },
+         {
+            "name":"some_plugin_call",
+            "type":"notification",
+            "provider":"ABC Plugin's details.",
+            "schema":{
+            }
+         }]
+      }
+   }
+
+
+
+.. _filemanager.get_files_registry:
+
+filemanager.get_files_registry
+------------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Fetch the registered config files within ATS. All configured files in the system will be retrieved by this API. This basically drops
+the `FileManager` binding files. File Manager keeps track of all the configured files in |TS|.
+
+Parameters
+~~~~~~~~~~
+
+* ``params``: Omitted
+
+Result
+~~~~~~
+
+An list of |object| with the following fields:
+
+
+``config_registry`` object:
+
+======================= ============= ===========================================
+Field                   Type          Description
+======================= ============= ===========================================
+``file_path``           |str|         File path, includes the full path and the file name configured in the record config name(if it's the case)
+``config_record_name``  |str|         Internal record config variable name.
+``parent_config``       |str|         Parent's configuration file name. e.g. If a top level remap.config includes additional mapping files,
+                                      then the top level file will be set in this field.
+``root_access_needed``  |str|         Elevated access needed.
+``is_required``         |str|         If it's required by |TS|. This specifies if |TS| treat this file as required to start the system(e.g. storage.config)
+======================= ============= ===========================================
+
+
+Examples
+~~~~~~~~
+
+Request:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "id":"14c10697-5b09-40f6-b7e5-4be85f64aa5e",
+      "jsonrpc":"2.0",
+      "method":"filemanager.get_files_registry"
+   }
+
+
+Response:
+
+.. code-block:: json
+   :linenos:
+
+   {
+      "jsonrpc":"2.0",
+      "result":{
+         "config_registry":[
+            {
+               "file_path":"/home/xyz/ats/etc/trafficserver/sni.yaml",
+               "config_record_name":"proxy.config.ssl.servername.filename",
+               "parent_config":"N/A",
+               "root_access_needed":"false",
+               "is_required":"false"
+            },
+            {
+               "file_path":"/home/xyz/ats/etc/trafficserver/storage.config",
+               "config_record_name":"",
+               "parent_config":"N/A",
+               "root_access_needed":"false",
+               "is_required":"true"
+            },
+            {
+               "file_path":"/home/xyz/ats/etc/trafficserver/jsonrpc3.yaml",
+               "config_record_name":"proxy.config.jsonrpc.filename",
+               "parent_config":"N/A",
+               "root_access_needed":"false",
+               "is_required":"false"
+            }
+         ]
+      }
+   }
+
+See also
+========
+
+:ref:`jsonrpc-node-errors`
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst
new file mode 100644
index 0000000..5f86054
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst
@@ -0,0 +1,485 @@
+.. 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
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. _JSON: https://www.json.org/json-en.html
+
+
+.. |str| replace:: ``string``
+.. |arraynum| replace:: ``array[number]``
+.. |arraystr| replace:: ``array[string]``
+.. |num| replace:: *number*
+.. |strnum| replace:: *string|number*
+.. |object| replace:: *object*
+.. |array| replace:: *array*
+.. |optional| replace:: ``optional``
+.. |arrayrecord| replace:: ``array[record]``
+.. |arrayerror| replace:: ``array[errors]``
+.. |RPC| replace:: JSONRPC 2.0
+
+Architecture
+************
+
+
+Protocol
+========
+
+The RPC mechanism implements the  `JSONRPC`_ protocol. You can refer to this section `jsonrpc-protocol`_ for more information.
+
+Server
+======
+
+.. _jsonrpc-architecture-ipc:
+
+IPC
+---
+
+The current server implementation runs on an IPC Socket(Unix Domain Socket). This server implements an iterative server style.
+The implementation runs on a dedicated ``TSThread`` and as their style express, this performs blocking calls to all the registered handlers.
+Configuration for this particular server style can be found in the admin section :ref:`admin-jsonrpc-configuration`.
+
+
+Using the JSONRPC mechanism
+===========================
+
+As a user, currently,  :program:`traffic_ctl` exercises this new protocol, please refer to the :ref:`traffic_ctl_jsonrpc` section.
+
+As a developer, please refer to the :ref:`jsonrpc_development` for a more detailed guide.
+
+
+
+JSON Parsing
+============
+
+Our JSONRPC  protocol implementation uses lib yamlcpp for parsing incoming and outgoing requests,
+this allows the server to accept either JSON or YAML format messages which then will be parsed by the protocol implementation. This seems handy
+for user that want to feed |TS| with existing yaml configuration without the need to translate yaml into json.
+
+.. note::
+
+   :program:`traffic_ctl` have an option to read files from disc and push them into |TS| through the RPC server. Files should be a
+   valid `JSONRPC`_ message. Please check `traffic_ctl rpc` for more details.
+
+
+In order to programs communicate with |TS| , This one implements a simple RPC mechanism to expose all the registered API handlers.
+
+You can check all current API by:
+
+   .. code-block:: bash
+
+      traffic_ctl rpc get-api
+
+or by using the ``show_registered_handlers`` API method.
+
+
+.. _jsonrpc-protocol:
+
+JSONRPC 2.0 Protocol
+====================
+
+JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. Primarily this specification defines several data structures
+and the rules around their processing. It is transport agnostic in that the concepts can be used within the same process, over sockets,
+over http, or in many various message passing environments. It uses JSON (RFC 4627) as data format.
+
+Overview
+========
+
+.. note::
+
+   Although most of the protocol specs are granted, we have implemented some exceptions. All the modifications will be properly documented.
+
+
+There are a set  of mandatory fields that must be included in a `JSONRPC`_ message as well as some optional fields, all this is documented here,
+you also can find this information in the `JSONRPC`_ link.
+
+.. _jsonrpc-request:
+
+Requests
+--------
+
+Please find the `jsonrpc 2.0 request` schema for reference ( `mgmt/rpc/schema/jsonrpc_request_schema.json` ).
+
+* Mandatory fields.
+
+
+   ============ ====== =======================================================================================
+   Field        Type   Description
+   ============ ====== =======================================================================================
+   ``jsonrpc``  |str|  Protocol version. |TS| follows the version 2.0 so this field should be only ``2.0``
+   ``method``   |str|  Method name that is intended to be invoked.
+   ============ ====== =======================================================================================
+
+
+* Optional parameters:
+
+
+   * ``params``:
+
+      A Structured value that holds the parameter values to be used during the invocation of the method. This member
+      may be omitted. If passed then a parameters for the rpc call must be provided as a Structured value.
+      Either by-position through an Array or by-name through an Object.
+
+      #. ``by-position`` |array|
+
+         params must be an ``array``, containing the values in the server expected order.
+
+
+         .. code-block:: json
+
+            {
+               "params": [
+                  "apache", "traffic", "server"
+               ]
+            }
+
+
+         .. code-block:: json
+
+            {
+               "params": [
+                  1, 2, 3, 4
+               ]
+            }
+
+
+         .. code-block:: json
+
+            {
+               "params": [{
+                  "name": "Apache"
+               },{
+                  "name": "Traffic"
+               },{
+                  "name": "Server"
+               }]
+            }
+
+      #. ``by-name``: |object|
+
+         Params must be an ``object``, with member names that match the server expected parameter names.
+         The absence of expected names may result in an error being generated by the server. The names must
+         match exactly, including case, to the method's expected parameters.
+
+         .. code-block:: json
+
+            {
+               "params": {
+                  "name": "Apache"
+               }
+            }
+
+   * ``id``: |str|.
+
+      An identifier established by the Client. If present, the request will be treated as a jsonrpc method and a
+      response should be expected from the server. If it is not present, the server will treat the request as a
+      notification and the client should not expect any response back from the server.
+      *Although a |number| can  be specified here we will convert this internally to a |str|. The response will be a |str|.*
+
+
+.. note::
+
+   The |RPC| protocol supports batch requests so, multiple independent requests can be send to the server in a single json message. Batch
+   request are basically an array of the above request, simply enclose them as an array ``[ .. ]``. The response for batch requests will be also
+   in a form of an array.
+
+.. _jsonrpc-response:
+
+Responses
+---------
+
+Although each individual API call will describe the response details and some specific errors, in this section we will describe a high
+level protocol response, some defined by the `JSONRPC`_ specs and some by |TS|
+
+Please find the `jsonrpc 2.0 response` schema for reference( `mgmt/rpc/schema/jsonrpc_response_schema.json` ).
+
+
+
+The responses have the following structure:
+
+
+   ============ ======== ==============================================
+   Field        Type     Description
+   ============ ======== ==============================================
+   ``jsonrpc``  |strnum| A Number that indicates the error type that occurred.
+   ``result``            Result of the invoked operation. See `jsonrpc-result`_
+   ``id``       |strnum| It will be the same as the value of the id member in the `jsonrpc-request`_ .
+                         We will not be using `null` if the `id` could not be fetch from the request,
+                         in that case the field will not be set.
+   ``error``    |object| Error object, it will be present in case of an error. See `jsonrpc-error`_
+   ============ ======== ==============================================
+
+Example 1:
+
+Request
+
+   .. code-block:: json
+
+      {
+         "jsonrpc": "2.0",
+         "result": ["hello", 5],
+         "id": "9"
+      }
+
+
+Response
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "error":{
+            "code":5,
+            "message":"Missing method field"
+         },
+         "id":"9"
+      }
+
+
+As the protocol specifies |TS| have their own set of error, in the example above it's clear that the incoming request is missing
+the method name, which |TS| sends a clear response error back.
+
+.. _jsonrpc-result:
+
+Result
+------
+
+
+* This member is required and will be present on success.
+* This member will not exist if there was an error invoking the method.
+* The value of this member is determined by the method invoked on the Server.
+
+In |TS| a RPC method that does not report any error and have nothing to send back to the client will use the following format to
+express that the call was successfully handled and the command was executed.
+
+
+.. _success_response:
+
+
+Example:
+
+   .. code-block:: json
+      :emphasize-lines: 4
+
+      {
+         "id": "89fc5aea-0740-11eb-82c0-001fc69cc946",
+         "jsonrpc": "2.0",
+         "result": "success"
+      }
+
+``"result": "success"`` will be set.
+
+.. _jsonrpc-error:
+
+Errors
+------
+
+The specs define the error fields that the client must expect to be sent back from the Server in case of any error.
+
+
+=============== ======== ==============================================
+Field           Type     Description
+=============== ======== ==============================================
+``code``        |num|    A Number that indicates the error type that occurred.
+``message``     |str|    A String providing a short description of the error.
+``data``        |object| This is an optional field that contains additional error data. Depending on the API this could contain data.
+=============== ======== ==============================================
+
+# data.
+
+This can be used for nested error so |TS| can inform a detailed error.
+
+   =============== ======== ==============================================
+   Field           Type     Description
+   =============== ======== ==============================================
+   ``code``        |str|    The error code. Integer type.
+   ``message``     |str|    The explanatory string for this error.
+   =============== ======== ==============================================
+
+
+
+
+Examples:
+
+# Fetch a config record response from |TS|
+
+Request:
+
+   .. code-block:: json
+
+      {
+         "id":"0f0780a5-0758-4f51-a177-752facc7c0eb",
+         "jsonrpc":"2.0",
+         "method":"admin_lookup_records",
+         "params":[
+            {
+               "record_name":"proxy.config.diags.debug.tags",
+               "rec_types":[
+                  "1",
+                  "16"
+               ]
+            }
+         ]
+      }
+
+Response:
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "recordList":[
+               {
+                  "record":{
+                     "record_name":"proxy.config.diags.debug.tags",
+                     "record_type":"3",
+                     "version":"0",
+                     "raw_stat_block":"0",
+                     "order":"423",
+                     "config_meta":{
+                        "access_type":"0",
+                        "update_status":"0",
+                        "update_type":"1",
+                        "checktype":"0",
+                        "source":"3",
+                        "check_expr":"null"
+                     },
+                     "record_class":"1",
+                     "overridable":"false",
+                     "data_type":"STRING",
+                     "current_value":"rpc",
+                     "default_value":"http|dns"
+                  }
+               }
+            ]
+         },
+         "id":"0f0780a5-0758-4f51-a177-752facc7c0eb"
+      }
+
+
+# Getting errors from |TS|
+
+Request an invalid record (invalid name)
+
+   .. code-block:: json
+
+      {
+         "id":"f212932f-b260-4f01-9648-8332200524cc",
+         "jsonrpc":"2.0",
+         "method":"admin_lookup_records",
+         "params":[
+            {
+               "record_name":"invalid.record",
+               "rec_types":[
+                  "1",
+                  "16"
+               ]
+            }
+         ]
+      }
+
+
+Response:
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "result":{
+            "errorList":[
+               {
+                  "code":"2000",
+                  "record_name":"invalid.record"
+               }
+            ]
+         },
+         "id":"f212932f-b260-4f01-9648-8332200524cc"
+      }
+
+
+Parse Error from an incomplete request
+
+   .. code-block::
+
+      {[[ invalid json
+
+
+   .. code-block:: json
+
+      {
+         "jsonrpc":"2.0",
+         "error":{
+            "code":-32700,
+            "message":"Parse error"
+         }
+      }
+
+
+
+Invalid method invocation.
+
+Request:
+
+   .. code-block:: json
+
+      {
+         "id":"f212932f-b260-4f01-9648-8332200524cc",
+         "jsonrpc":"2.0",
+         "method":"some_non_existing_method",
+         "params":{
+
+         }
+      }
+
+Response:
+
+   .. code-block::json
+
+      {
+         "error": {
+            "code": -32601,
+            "message": "Method not found"
+         },
+         "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+         "jsonrpc": "2.0"
+      }
+
+
+.. information:
+
+   According to the |RPC| specs, if you get an error, the ``result`` field will not be set. |TS| will grant this.
+
+
+
+Development Guide
+=================
+
+* For details on how to implement JSONRPC  handler and expose them through the rpc server, please refer to :ref:`jsonrpc_development`.
+* If you need to call a new JSONRPC API through :program:`traffic_ctl`, please refer to :ref:`developer-guide-traffic_ctl-development`
+* To interact directly with the |RPC| node, please check :ref:`jsonrpc-node`
+
+See also
+========
+
+:ref:`admin-jsonrpc-configuration`,
+:ref:`jsonrpc-node-errors`,
+:ref:`traffic_ctl_jsonrpc`
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-client-api.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-client-api.en.rst
new file mode 100644
index 0000000..80f08c7
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-client-api.en.rst
@@ -0,0 +1,56 @@
+.. 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
+
+.. |RPC| replace:: JSONRPC 2.0
+
+.. _YAML: https://github.com/jbeder/yaml-cpp/wiki/Tutorial
+
+.. _developer-guide-jsonrpc-client-api:
+
+Traffic Server JSONRPC Node C++ Client Implementation
+*****************************************************
+
+Basics
+======
+
+|TS| provides a set of basic C++ classes to perform request and handle responses from
+server's rpc node.
+Files under `include/shared/rpc` are meant to be used by client applications like :program:`traffic_ctl`
+and :program:`traffic_top` to send a receive messages from the |TS| jsonrpc node.
+
+This helper classes provides:
+
+  * ``RPCClient`` class which provides functionality to connect and invoke remote command inside the |TS| |RPC| node.
+    This class already knows where the unix socket is located.
+
+  * ``IPCSocketClient`` class which provides the socket implementation for the IPC socket in the |RPC| node. If what
+    you want is just to invoke a remote function and get the response, then it's recommended to just use the ``RPCClient``
+    class instead.
+
+  * ``RPCRequests`` class which contains all the basic classes to map the basic |RPC| messages(requests and responses).
+    If what you want is a custom message you can just subclass ``shared::rpc::ClientRequest`` and override the ``get_method()``
+    member function. You can check ``CtrlRPCRequests.h`` for examples.
+    The basic encoding and decoding for these structures are already implemented inside ``yaml_codecs.h``. In case you define
+    your own message then you should provide you own codec implementation, there are some examples available in ``ctrl_yaml_codecs.h``
+
+Building
+========
+
+..
+  _ TBC.
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst
new file mode 100644
index 0000000..c87f8e4
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst
@@ -0,0 +1,595 @@
+.. 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
+
+.. |RPC| replace:: JSONRPC 2.0
+
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. _JSON: https://www.json.org/json-en.html
+.. _YAML: https://github.com/jbeder/yaml-cpp/wiki/Tutorial
+
+.. _jsonrpc_development:
+
+Handler implementation
+**********************
+
+Use this section as a guide for developing new rpc methods inside |TS| and how to expose them through the |RPC| endpoint.
+Before we start, it is worth mentioning some of the architecture of the current implementation. The whole RPC mechanism is divided in
+few components.
+
+Json RPC manager
+================
+
+This class is the entrance point for both server calls and registered functions.
+
+.. figure:: ../../uml/images/JsonRPCManager.svg
+
+Dispatcher class
+----------------
+
+* Class that keeps track of all the registered methods and notifications that can be invoked by the RPC server. This class holds two
+  hash tables containing methods and notifications which use the method name as a key.
+* This class internally consumes ``RPCRequestInfo`` objects and performs the invocation of the respective calls.
+* This class handlers the responses from the registered callbacks and it fills the appropriated ``RPCResponseInfo`` which then is passed
+  back to the ``JsonRPCManager`` class.
+
+
+JsonRPCManager class
+--------------------
+
+* Singleton class that handles the JSONRPC handler registration and JSONRPC handler invocation.
+* This class is the main entrance point for the RPC server through the ``handle_call(std::string_view req)`` function.
+* This class is the main entrance point for the handler to be able to register in the RPC logic. ``add_notification_handler`` and ``remove_notification_handler``.
+
+
+Implementing new handlers
+=========================
+
+There a a few basic concepts that needs to be known before implementing a new handler, this is an easy process and the complexity depends on
+the nature of the handler that you want to implement.
+Dealing with incoming and outgoing parameters is up to the developer, we will touch on some ways to deal with this through this guide.
+
+.. _jsonrpc_development-design:
+
+Design
+------
+
+As requirement from the ``JsonRPCManager`` in order to be able to register inside the RPC management a function should implement the
+following signature:
+
+Methods:
+
+.. code-block:: cpp
+
+    ts::Rv<YAML::Node> your_rpc_handler_function_name(std::string_view const &id, YAML::Node const &params);
+
+    // Plugins:
+    void (*TSRPCMethodCb)(const char *id, TSYaml params);
+
+
+Notifications:
+
+.. code-block:: cpp
+
+    void your_rpc_handler_function_name(YAML::Node const &params);
+
+    // Plugins:
+    void (*TSRPCNotificationCb)(TSYaml params);
+
+
+* Incoming method request's id will be passed to the handler, this is read only value as the server is expected to respond with the same value.
+* ``YAML::Node`` params is expected to be a ``Sequence`` or a ``Map``, as per protocol this cannot be a single value, so do not expect things like:
+  ``param=123`` or ``param=SomeString``.
+* The ``params`` can be empty and contains no data at all.
+
+.. note::
+
+    Plugins should cast ``TSYaml`` to a ``YAML::Node``. Regarding the `yamlcpp` library we will provide binary
+    compatibility within the lifespan of a major release only.
+
+
+It is important to know that ``method`` handlers are expected to respond to the requests, while ``notifications``` will not respond with
+any data nor error. You can find more information in :ref:`jsonrpc-protocol` or directly in the protocol specs `JSONRPC`_.
+
+
+.. note::
+
+    If there is no explicit response from the method, the protocol implementation will respond with `success_response` unless an error
+    was specified.
+
+
+Registration and Handling
+-------------------------
+
+JSONRPC Manager API
+~~~~~~~~~~~~~~~~~~~
+
+Handler registration should be done by using the ``JsonRPCManager`` singleton object. Note that there are a set of convenient helper
+functions that can be used to achieve registration through the singleton object.
+
+.. code-block:: cpp
+
+    namespace rpc {
+        // this set of functions will call the singleton object and perform the same as by using the singleton directly.
+        add_method_handler(...)
+        add_notification_handler(...)
+    }
+
+
+.. code-block:: cpp
+
+    // Handler implementation
+    ts::Rv<YAML::Node>
+    my_handler_impl(std::string_view const &id, YAML::Node const &params)
+    {
+        using namespace rpc::handlers::errors;
+        return make_errata(Codes::SERVER, "Something happened in the server");
+    }
+
+The actual registration:
+
+.. code-block:: cpp
+
+    #include "rpc/jsonrpc/JsonRPC.h"
+    ...
+    rpc::add_method_handler("my_handler_impl", &my_handler_impl);
+
+
+This API also accepts a RPCRegistryInfo pointer which will provide a context data for the particular handler, for instance it will
+display the provider's name when the service descriptor gets called. There is a global object created for this purpose which can be used
+As a generic registry context object,  ``core_ats_rpc_service_provider_handle`` is defined in the  ``JsonRPC.h`` header. Please check
+:ref:`get_service_descriptor` for more information.
+
+
+Notification example:
+
+As mentioned before, notifications do not need to respond, as they are "fire and forget" calls, no id will be provided as part of the api.
+
+.. code-block:: cpp
+
+    void
+    my_notification_handler(YAML::Node const &params)
+    {
+        // do something
+        // all errors will be ignored by the server.
+    }
+
+Registration for notifications uses a different API:
+
+.. code-block:: cpp
+
+    #include "rpc/jsonrpc/JsonRPC.h"
+    rpc::add_notification_handler("my_notification_handler", &my_notification_handler);
+
+
+
+The registration API allows the client to pass a  ``RPCRegistryInfo`` which provides extra information for a particular handler. Non plugins handlers
+should use the default provided Registry Info, located in the `JsonRPC.h` header file, ``core_ats_rpc_service_provider_handle``. Plugins should use the
+one created by ``TSRPCRegister``
+
+Plugin API
+~~~~~~~~~~
+
+
+Plugins have a different API to register and handle RPC. Unlike registering and handling rpc using the JSONRPC Manager directly, this
+API provides a different approach, it's understood that plugins may not be able to respond a rpc with a valid response at the very
+same moment that they are called, this could be because the rpc needs to perform an intensive operation or because the data that should
+be returned by the handler is not yet ready, in any case plugin have the flexibility to say when they finished processing the request, either with
+a successful response or with an error. **The JSONRPC manager will wait for the plugin to "mark" the current call as done**.
+
+.. note:
+
+    Check :c:func:`TSRPCRegister` for API details.
+
+
+RPC method registration and implementation examples
+
+#. No reschedule work on the rpc handler, we set the response in the same call. This runs on the RPC thread.
+
+    .. code-block:: cpp
+
+        #include <ts/ts.h>
+
+        namespace {
+            static const std::string MY_YAML_VERSION{"0.7.0"};
+        }
+
+        void
+        my_join_string_handler(const char *id, TSYaml p)
+        {
+            YAML::Node params = *(YAML::Node *)p;
+            // extract the strings from the params node
+            std::vector<std::string> passedStrings;
+            if (auto node = params["strings"]) {
+                passedStrings = node.as<std::vector<std::string>>();
+            } else {
+                // We can't continue, let the JSONRPC Manager know that we have finished
+                TSRPCHandlerError(NO_STRINGS_ERROR_CODE, " no strings field passed");
+                return;
+            }
+
+            std::string join;
+            std::for_each(std::begin(passedStrings), std::end(passedStrings), [&join](auto &&s) { join += s; });
+            YAML::Node resp; // start building the response.
+            resp["join"] = join; // add the join string into a "join" field.
+            TSRPCHandlerDone(reinterpret_cast<TSYaml>(&resp)); // Let the JSONRPC Manager know that we have finished
+        }
+
+        void
+        TSPluginInit(int argc, const char *argv[])
+        {
+            ...
+            // Check-in to make sure we are compliant with the YAML version in TS.
+            TSRPCProviderHandle rpcRegistrationInfo = TSRPCRegister("My plugin's info", "0.7.0");
+            if (rpcRegistrationInfo == nullptr) {
+                TSError("[%s] RPC handler registration failed, yaml version not supported.", PLUGIN_NAME);
+            }
+
+            if (TSRPCRegisterMethodHandler("join_strings", my_join_string_handler, rpcRegistrationInfo) == TS_ERROR) {
+                TSDebug(PLUGIN_NAME, "%s failed to register", rpcCallName.c_str());
+            } else {
+                TSDebug(PLUGIN_NAME, "%s successfully registered", rpcCallName.c_str());
+            }
+        }
+
+#. RPC handling rescheduled to run on a ET TASK thread.
+
+    .. code-block:: cpp
+
+        #include <ts/ts.h>
+
+        namespace {
+            static const std::string MY_YAML_VERSION{"0.7.0"};
+        }
+
+        int
+        merge_yaml_file_on_et_task(TSCont contp, TSEvent event, void *data)
+        {
+            // read the incoming node and merge it with the copy on disk.
+            YAML::Node params = *static_cast<YAML::Node *>(TSContDataGet(contp));
+
+            // we only care for a map type {}
+            if (params.Type() != YAML::NodeType::Map) {
+                TSRPCHandlerError(INVALID_PARAM_TYPE_CODE, "Handler is expecting a map.");
+                return TS_SUCCESS;
+            }
+
+            // do the actual work.
+            merge_yaml(params);
+            // ...
+            YAML::Node resp;
+            TSContDestroy(contp);
+            TSRPCHandlerDone(reinterpret_cast<TSYaml>(&resp));
+        }
+
+        void
+        merge_yaml_file(const char *id, TSYaml p)
+        {
+            TSCont c = TSContCreate(merge_yaml_file_on_et_task, TSMutexCreate());
+            TSContDataSet(c, p);
+            TSContScheduleOnPool(c, 3000 /* no particular reason */, TS_THREAD_POOL_TASK);
+        }
+
+        void
+        TSPluginInit(int argc, const char *argv[])
+        {
+            // ...
+            // Check-in to make sure we are compliant with the YAML version in TS.
+            TSRPCProviderHandle rpcRegistrationInfo = TSRPCRegister("My plugin's info", "0.7.0");
+            if (rpcRegistrationInfo == nullptr) {
+                TSError("[%s] RPC handler registration failed, yaml version not supported.", PLUGIN_NAME);
+            }
+
+            if (TSRPCRegisterMethodHandler("merge_yaml_file", merge_yaml_file, rpcRegistrationInfo) == TS_ERROR) {
+                TSDebug(PLUGIN_NAME, "%s failed to register", rpcCallName.c_str());
+            } else {
+                TSDebug(PLUGIN_NAME, "%s successfully registered", rpcCallName.c_str());
+            }
+        }
+
+
+Error Handling
+--------------
+
+
+JSONRPC Manager API
+~~~~~~~~~~~~~~~~~~~
+
+There are several ways to deal with internal handler errors. Errors are expected to be sent back to the client if the API was expressed that way
+and if the request was a ``method``.
+We have defined some generic errors that can be used to respond depending on the nature of the registered handler,
+please check :ref:`jsonrpc-handler-errors` for more info.
+
+We recommend some ways to deal with this:
+
+#. Using the ``Errata`` from ``ts::Rv<YAML::Node>``
+
+This can be set in case you would like to let the server to respond with an |RPC| error, ``ExecutionError`` will be used to catch all the
+errors that are fired from within the function call, either by setting the proper errata or by throwing an exception.
+Please check the :ref:`jsonrpc-node-errors` and in particular ``ExecutionError``. Also check :ref:`jsonrpc-handler-errors`
+
+.. important::
+
+    Errors have preference over any other response, so if you set both, the errata and the ``YAML::Node`` response, then the former
+    will be set in the response.
+
+#. Defining a custom error object and make it part of the response object.
+
+* This is up to the developer and the errors can be part of the response ``YAML::Node``.
+* The JSONRPC Dispatcher will read that there is no error returned from the call and use the result to build the response. If this is
+  what you are willing to respond to, then make sure that the error is not set in the ``ts::Rv``.
+
+
+#. Exception.
+
+    As long as the exception inherit from ``std::exception`` it will be handled by the jsonrpc manager, this error will be
+    handled as like using the ``Errata`` object, this kind of errors will be part of the ``ExecutionError``.
+
+    The following example will generate this JSON response:
+
+    .. code-block:: cpp
+
+        ts::Rv<YAML::Node>
+        foo(std::string_view const &id, YAML::Node const &params)
+        {
+           some_unhandled_operation_that_throws();
+        }
+
+
+    .. code-block::json
+
+        {
+            "jsonrpc":"2.0",
+            "error":{
+                "code":9,
+                "message":"Error during execution"
+            },
+            "id":"abcd-id"
+        }
+
+
+Examples:
+
+* Respond with an error, no ``result`` field will be set in the response.
+
+    .. code-block::
+
+        ts::Rv<YAML::Node>
+        respond_with_an_error(std::string_view const &id, YAML::Node const &params)
+        {
+            using namespace rpc::handlers::errors;
+            return make_errata(Codes::SERVER, "Something happened in the server");
+        }
+
+    Server's response.
+
+    .. code-block:: json
+
+        {
+            "jsonrpc":"2.0",
+            "error":{
+                "code":9,
+                "message":"Error during execution",
+                "data":[
+                    {
+                        "code":3000,
+                        "message":"Something happened in the server"
+                    }
+                ]
+            },
+            "id":"abcd-id"
+        }
+
+    .. note::
+
+        ``make_errata`` hides some internal details when creating an errata.
+
+* Response with custom handler error. In this case, make sure that the API definition and documentation reflects this as so far we do not
+  have json schemas to enforce any of this on the client side.
+
+
+   .. code-block::
+
+      ts::Rv<YAML::Node>
+      respond_with_my_own_error(std::string_view const &id, YAML::Node const &params)
+      {
+          YAML::Node resp;
+          resp["HandlerErrorDescription"] = "I can set up my own error in the result field.";
+          return resp;
+      }
+
+    The "error" is part of the ``result``, in this case this could be used as any other field, the example would be the same.
+
+   .. code-block:: json
+
+      {
+        "jsonrpc":"2.0",
+        "result":{
+            "HandlerErrorDescription":"I can set up my own error in the result field."
+        },
+        "id":"abcd-id"
+      }
+
+
+We have selected the ``ts::Rv<YAML::Node>`` as a message interface as this can hold the actual response/error.
+
+
+Plugin API
+~~~~~~~~~~
+
+When implementing rpc handlers inside a plugin, errors should be handled differently:
+
+
+.. code-block::
+
+    #include <ts/ts.h>
+
+    ...
+
+    void
+    foo_handler(const char *id, TSYaml p)
+    {
+        // read the incoming node and merge it with the copy on disk.
+        YAML::Node params = *static_cast<YAML::Node *>(TSContDataGet(contp));
+
+        if (check_if_error(params)) {
+            TSRPCHandlerError(FOO_ERROR_CODE, "Some error descr.");
+            return;
+        }
+
+        ...
+    }
+
+
+.. note::
+
+    Errors can be a part of the request response(result field), this depends on the API design. The above example
+    shows how to set an error that will be sent back as part of the Error field.
+
+
+For more information check the  :c:func:`TSRPCRegister` for API details and :ref:`jsonrpc-error`.
+
+.. _jsonrpc-handler-unit-test:
+
+Unit test
+=========
+
+All new methods exposed through the RPC server can be tested using the jsonrpc autest extension.
+
+jsonrpc_client.test.text
+------------------------
+
+This extension provides the ability to interact with the JSONRPC interface by using :program:`traffic_ctl` as a client. As a helper
+for all new autest that needs to write and read jsonrpc message, there is also a new module `jsonrpc.py` which provides
+a nice and easy interface to write methods and notifications.
+This extension also provides the facility to write custom jsonrpc validations. Please check some of the following examples:
+
+
+#. Write custom jsonrpc message
+
+    .. code-block:: python
+
+        '''
+        The newly added jsonrpc method was named 'foo_bar' and is expected to accept a list of fqdn.
+        '''
+        tr = Test.AddTestRun("Test JSONRPC foo_bar()")
+        '''
+        The following call to the Request object will generate this:
+        {
+            "id": "850d32a8-d5a7-11eb-bebc-fa163e6d2ec5",
+            "jsonrpc": "2.0",
+            "method": "foo_bar",
+            "params": {
+                "fqdn": ["yahoo.com", "trafficserver.org"]
+            }
+        }
+        '''
+        req = Request.foo_bar(fqdn=["yahoo.com", "trafficserver.org"])
+        tr.AddJsonRPCClientRequest(ts, req)
+
+
+#. Custom response validation
+
+    .. code-block:: python
+
+        tr = Test.AddTestRun("Test update_host_status")
+
+        Params = [
+            {'name': 'yahoo', 'status': 'up'}
+        ]
+
+        tr.AddJsonRPCClientRequest(ts, Request.update_host_status(hosts=Params))
+
+        def check_no_error_on_response(resp: Response):
+            # we only check if it's an error.
+            if resp.is_error():
+                return (False, resp.error_as_str())
+            return (True, "All good")
+
+        tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(check_no_error_on_response)
+
+
+
+CustomJSONRPCResponse
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A tester class that will let you write your own response validation by dealing with the jsonrpc.Response object,
+please check the ``CustomJSONRPCResponse`` tester for more information.
+
+
+AddJsonRPCClientRequest
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This function will generate a json response as an output, internally it uses :program:`traffic_ctl rpc file --format json` as client.
+The output can be used and compared with a gold file. This also provides an optional schema validation for the entire JSONRPC protocol
+as well as the ``param`` field against a specific schema file. You can specify ``schema_file_name`` with a valid json schema file to validate
+the entire JSONRPC 2.0 request(except the content of the ``params`` field). You can also set ``params_field_schema_file_name`` with a
+valid json schema file to validate only the ``params`` field.
+
+Example:
+
+    The following, beside sending the request, will perform a JSONRPC 2.0 schema validation as well as the ``param`` field.
+
+    .. code-block:: python
+
+        schema_file_name = 'schemas/jsonrpc20_request_schema.json'
+        params_schema_file_name = 'schemas/join_strings_request_params_schema.json'
+        tr.AddJsonRPCClientRequest(
+            ts,
+            file="join_strings_request.json",
+            schema_file_name=schema_file_name,
+            params_field_schema_file_name=params_schema_file_name)
+
+
+JSONRPCResponseSchemaValidator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A tester class to perform a schema validation of the entire JSONRPC 2.0 request as well as the ``result`` param.
+You can specify ``schema_file_name`` with a valid json schema file to validate the entire JSONRPC 2.0 response(except the content of the ``result`` field).
+Also you can set ``result_field_schema_file_name`` with a valid json schema file to validate only the ``result`` field.
+
+
+Example:
+
+    The following will add a Tester for a JSONRPC 2.0 schema validation as well as the ``result`` field.
+
+    .. code-block:: python
+
+        tr = Test.AddTestRun('test valid response schema')
+        schema_file_name = 'schemas/jsonrpc20_response_schema.json'
+        result_schema_file_name = 'schemas/join_strings_request_result_schema.json'
+
+        # Add a tester.
+        tr.Processes.Default.Streams.stdout = Testers.JSONRPCResponseSchemaValidator(
+            schema_file_name=response_schema_file_name,
+            result_field_schema_file_name=result_schema_file_name)
+
+Important Notes
+===============
+
+* You can refer to `YAML`_ for more info in how code/decode yaml content.
+* Remember to update :ref:`jsonrpc-api` if you are adding a new handler.
+* If a new handler needs to be exposed through :program:`traffic_ctl` please refer to :ref:`traffic_ctl_jsonrpc` for a general idea
+  and to :ref:`developer-guide-traffic_ctl-development` for how to implement a new command.
+* To interact directly with the |RPC| node, please check :ref:`jsonrpc-node`
+
+
+See also
+========
+
+:ref:`admin-jsonrpc-configuration`
+:ref:`jsonrpc-protocol`
+:ref:`developer-guide-traffic_ctl-development`
+
+
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-node-errors.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-node-errors.en.rst
new file mode 100644
index 0000000..079fe54
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-node-errors.en.rst
@@ -0,0 +1,145 @@
+.. 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
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. _JSON: https://www.json.org/json-en.html
+
+
+.. _jsonrpc-node-errors:
+
+
+JSON RPC errors
+***************
+
+A list of codes and descriptions of errors that could be send back from the JSONRPC server. JSONRPC response messages could contains
+different set of errors in the following format:
+
+.. note::
+
+   Check  :ref:`jsonrpc-error` for details about the error structure.
+
+
+.. code-block::json
+
+   {
+      "error": {
+         "code": -32601,
+         "message": "Method not found"
+      },
+      "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+      "jsonrpc": "2.0"
+   }
+
+In some cases the data field could be populated:
+
+.. code-block:: json
+
+   {
+      "jsonrpc": "2.0",
+      "error":{
+         "code": 10,
+         "message": "Unauthorized action",
+         "data":[
+            {
+               "code": 2,
+               "message":"Denied privileged API access for uid=XXX gid=XXX"
+            }
+         ]
+      },
+      "id":"5e273ec0-3e3b-4a81-90ec-aeee3d38073f"
+   }
+
+
+.. _jsonrpc-node-errors-standard-errors:
+
+Standard errors
+===============
+
+=============== ========================================= =========================================================
+Id              Message                                   Description
+=============== ========================================= =========================================================
+-32700          Parse error                               Invalid JSON was received by the server.
+                                                          An error occurred on the server while parsing the JSON text.
+-32600          Invalid Request                           The JSON sent is not a valid Request object.
+-32601          Method not found                          The method does not exist / is not available.
+-32602          Invalid params                            Invalid method parameter(s).
+-32603          Internal error                            Internal `JSONRPC`_ error.
+=============== ========================================= =========================================================
+
+.. _jsonrpc-node-errors-custom-errors:
+
+Custom errors
+=============
+
+The following error list are defined by the server.
+
+=============== ========================================= =========================================================
+Id              Message                                   Description
+=============== ========================================= =========================================================
+1               Invalid version, 2.0 only                 The server only accepts version field equal to `2.0`.
+2               Invalid version type, should be a string  Version field should be a literal string.
+3               Missing version field                     No version field present, version field is mandatory.
+4               Invalid method type, should be a string   The method field should be a literal string.
+5               Missing method field                      No method field present, method field is mandatory.
+6               Invalid params type. A Structured value   Params field should be a structured type, list or structure.
+                is expected                               This is similar to `-32602`
+7               Invalid id type                           If field should be a literal string.
+8               Use of null as id is discouraged          Id field value is null, as per the specs this is discouraged,
+                                                          the server will not accept it.
+9               Error during execution                    An error occurred during the execution of the RPC call.
+                                                          This error is used as a generic High level error. The specifics
+                                                          details about the error, in most cases are specified in the
+                                                          ``data`` field.
+10              Unauthorized action                       The rpc method will not be invoked because the action is not
+                                                          permitted by some constraint or authorization issue.Check
+                                                          :ref:`jsonrpc-node-errors-unauthorized-action` for mode details.
+11              Use of an empty string as id is           An empty string "" as an id will not be accepted by the server.
+                discouraged
+=============== ========================================= =========================================================
+
+.. _jsonrpc-node-errors-unauthorized-action:
+
+Unauthorized action
+-------------------
+
+Under this error, the `data` field could be populated with the following errors, eventually more than one could be in set.
+
+.. code-block:: json
+
+   "data":[
+      {
+         "code":2,
+         "message":"Denied privileged API access for uid=XXX gid=XXX"
+      }
+   ]
+
+=============== ========================================= =========================================================
+Id              Message                                   Description
+=============== ========================================= =========================================================
+1               Error getting peer credentials: {}        Something happened while trying to get the peers credentials.
+                                                          The error string will show the error code(`errno`) returned by the
+                                                          server.
+2               Denied privileged API access for uid={}   Permission denied. Unix Socket credentials were checked and they haven't meet
+                gid={}                                    the required policy. The handler was configured as restricted
+                                                          and the socket credentials failed to validate. Check TBC for
+                                                          more information.
+=============== ========================================= =========================================================
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst
new file mode 100644
index 0000000..25b63a5
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst
@@ -0,0 +1,64 @@
+.. 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
+
+.. _JSON: https://www.json.org/json-en.html
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. |RPC| replace:: JSONRPC 2.0
+
+
+.. _jsonrpc-node:
+
+JSONRPC Endpoint
+****************
+
+Traffic Server API clients can use different languages to connect and interact with the |RPC| node directly.
+The goal of this section is to provide some tips on how to work with it.
+To begin with, you should be familiar with the |RPC| protocol, you can check here :ref:`jsonrpc-protocol` and also `JSONRPC`_ .
+
+
+IPC Node
+========
+
+You can directly connect to the Unix Domain Socket used for the |RPC| node, the location of the sockets
+will depend purely on how did you configure the server, please check :ref:`admin-jsonrpc-configuration` for
+information regarding configuration.
+
+
+Socket connectivity
+-------------------
+
+|RPC| server will close the connection once the server processed the incoming requests, so clients should be aware
+of this and if sending multiple requests they should reconnect to the node once the response arrives. The protocol
+allows you to send a bunch of requests together, this is called batch messages so it's recommended to send them all instead
+of having a connection open and sending requests one by one. This being a local socket opening and closing the connection should
+not be a big concern.
+
+
+
+Using traffic_ctl
+-----------------
+
+:program:`traffic_ctl` can also be used to directly send raw |RPC| messages to the server's node, :program:`traffic_ctl` provides
+several options to achieve this, please check ``traffic_ctl_rpc``.
+
+
+Error responses
+---------------
+
+The server will indicate in case of any error processing the call, check :ref:`jsonrpc-node-errors` for more details.
diff --git a/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst b/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst
new file mode 100644
index 0000000..957c00a
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst
@@ -0,0 +1,217 @@
+.. 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
+
+.. |RPC| replace:: JSONRPC 2.0
+
+.. _YAML: https://github.com/jbeder/yaml-cpp/wiki/Tutorial
+
+.. _developer-guide-traffic_ctl-development:
+
+Traffic Control Development Guide
+*********************************
+
+Traffic Control interacts with |TS| through the |RPC| endpoint. All interaction is done by following the |RPC| protocol.
+
+Overall structure
+=================
+
+.. figure:: ../../uml/images/traffic_ctl-class-diagram.svg
+
+
+* The whole point is to separate the command handling from the printing part.
+* Printing should be done by an appropriate Printer implementation, this should support several kinds of printing formats.
+* For now, everything is printing in the standard output, but a particular printer can be implemented in such way that
+  the output could be sent to a different destination.
+* JSONRPC requests have a base class that hides some of the basic and common parts, like ``id``, and ``version``. When deriving
+  from this class, the only thing that needs to be override is the ``method``
+
+
+.. important::
+
+   CtrlCommand will invoke ``_invoked_func`` when executed, this should be set by the derived class
+
+* The whole design is that the command will execute the ``_invoked_func`` once invoked. This function ptr should be set by the
+  appropriated derived class based on the passed parameters. The derived class have the option to override the execute() which
+  is a ``virtual`` function and does something else. Check ``RecordCommand`` as an example.
+
+
+Command implementation
+======================
+
+#. Add the right command to the ``ArgParser`` object inside the ``traffic_ctl.cc``.
+#. If needed, define a new ``Command`` derived class inside the ``CtrlCommands`` file. if it's not an new command level, and it's a subcommand,
+   then you should check the existing command to decide where to place it.
+
+      * Implement the member function that will be dealing with the particular command, ie: (config_status())
+
+      * If a new JsonRPC Message needs to be done, then implement it by deriving from ``shared::rpc::ClientRequest`` if a method is needed, or from
+        ``shared::rpc::ClientRequestNotification`` if it's a notification.         More info can be found here :ref:`jsonrpc-request` and
+        :ref:`jsonrpc_development-design`. This can be done inside the ``RPCRequest.h`` file.
+
+
+      .. note::
+
+         Make sure you override the ``std::string get_method() const`` member function with the appropriate api method name.
+
+
+#. If needed define a new ``Printer`` derived class inside  the ``CtrlPrinter`` file.
+
+   * If pretty printing format will be supported, then make sure you read the ``_format`` member you get from the ``BasePrinter`` class.
+
+#. If it's a new command level (like config, metric, etc), make sure you update the ``Command`` creation inside the ``traffic_ctl.cc`` file.
+
+Implementation Example
+======================
+
+Let's define a new command for a new specific API with name == ``admin_new_command_1`` with the following json structure:
+
+.. code-block: json
+
+   {
+      "id":"0f0780a5-0758-4f51-a177-752facc7c0eb",
+      "jsonrpc":"2.0",
+      "method":"admin_new_command_1",
+      "params":{
+         "named_var_1":"Some value here"
+      }
+   }
+
+
+.. code-block:: bash
+
+   $ traffic_ctl new-command new-subcommand1
+
+#. Update ``traffic_ctl.cc``. I will ignore the details as they are trivial.
+
+#. Define a new Request.
+
+   So based on the above json, we can model our request as:
+
+   .. code-block:: cpp
+
+      // RPCRequests.h
+      struct NewCommandJsonRPCRequest : shared::rpc::ClientRequest {
+         using super = shared::rpc::ClientRequest;
+         struct Params {
+            std::string named_var_1;
+         };
+         NewCommandJsonRPCRequest(Params p)
+         {
+            super::params = p; // This will invoke the built-in conversion mechanism in the yamlcpp library.
+         }
+         // Important to override this function, as this is the only way that the "method" field will be set.
+         std::string
+         get_method() const {
+            return "admin_new_command_1";
+         }
+      };
+
+#. Implement the yamlcpp convert function, Yaml-cpp has a built-in conversion mechanism. You can refer to `YAML`_ for more info.
+
+   .. code-block:: cpp
+
+      // yaml_codecs.h
+      template <> struct convert<NewCommandJsonRPCRequest::Params> {
+         static Node
+         encode(NewCommandJsonRPCRequest::Params const &params)
+         {
+            Node node;
+            node["named_var_1"] = params.named_var_1;
+            return node;
+         }
+      };
+
+#. Define a new command. For the sake of simplicity I'll only implement it in the ``.h`` files.
+
+   .. code-block:: cpp
+
+      // CtrlCommands.h & CtrlCommands.cc
+      struct NewCommand : public CtrlCommand {
+         NewCommand(ts::Arguments args): CtrlCommand(args)
+         {
+            // we are interested in the format.
+            auto fmt = parse_format(_arguments);
+            if (args.get("new-subcommand1") {
+               // we need to create the right printer.
+               _printer = std::make_sharec<MyNewSubcommandPrinter>(fmt);
+               // we need to set the _invoked_func that will be called when execute() is called.
+               _invoked_func = [&]() { handle_new_subcommand1(); };
+            }
+            // if more subcommands are needed, then add them here.
+         }
+
+      private:
+         void handle_new_subcommand1() {
+            NewCommandJsonRPCRequest req{};
+            // fill the req if needed.
+            auto response = invoke_rpc(req);
+            _printer->write_output(response);
+         }
+      };
+
+#. Define a new printer to deal with this command. We will assume that the printing will be different for every subcommand.
+   so we will create our own one.
+
+
+   .. code-block:: cpp
+
+      class MyNewSubcommandPrinter : public BasePrinter
+      {
+         void write_output(YAML::Node const &result) override {
+            // result will contain what's coming back from the server.
+         }
+      };
+
+   In case that the format type is important, then we should allow it by accepting the format being passed in the constructor.
+   And let it set the base one as well.
+
+   .. code-block:: cpp
+
+      MyNewSubcommandPrinter(BasePrinter::Format fmt) : BasePrinter(fmt) {}
+
+
+
+   The way you print and the destination of the message is up to the developer's needs, either a terminal or some other place. If the response
+   from the server is a complex object, you can always model the response with your own type and use the built-in yamlcpp mechanism
+   to decode the ``YAML::Node``.  ``write_output(YAML::Node const &result)`` will only have the result defined in the protocol,
+   check :ref:`jsonrpc-result` for more detail. So something like this can be easily achieved:
+
+   .. code-block:: cpp
+
+         void
+         GetHostStatusPrinter::write_output(YAML::Node const &result)
+         {
+            auto response = result.as<NewCommandJsonRPCRResponse>(); // will invoke the yamlcpp decode.
+            // you can now deal with the Record object and not with the yaml node.
+         }
+
+Notes
+=====
+
+There is code that was written in this way by design, ``RecordPrinter`` and ``RecordRequest`` are meant to be use by any command
+that needs to query and print records without any major hassle.
+
+
+See also
+========
+
+:ref:`admin-jsonrpc-configuration`,
+:ref:`traffic_ctl_jsonrpc`,
+:ref:`jsonrpc_development`,
+:ref:`jsonrpc-node-errors`
diff --git a/doc/developer-guide/layout/runroot.en.rst b/doc/developer-guide/layout/runroot.en.rst
index 07c73bf..9ad2e12 100644
--- a/doc/developer-guide/layout/runroot.en.rst
+++ b/doc/developer-guide/layout/runroot.en.rst
@@ -52,7 +52,7 @@
 #. Compiler defaults in layout class
 
 Right now, the following programs are integrated with the runroot logic:
-**traffic_server**, **traffic_manager**, **traffic_ctl**, **traffic_layout**, **traffic_crashlog**, **traffic_logcat**, **traffic_logstat**.
+**traffic_server**, **traffic_ctl**, **traffic_layout**, **traffic_crashlog**, **traffic_logcat**, **traffic_logstat**.
 
 The YAML file
 =============
diff --git a/doc/developer-guide/plugins/continuations/activating-continuations.en.rst b/doc/developer-guide/plugins/continuations/activating-continuations.en.rst
index 6b4135f..8ff2448 100644
--- a/doc/developer-guide/plugins/continuations/activating-continuations.en.rst
+++ b/doc/developer-guide/plugins/continuations/activating-continuations.en.rst
@@ -23,7 +23,7 @@
 ************************
 
 Continuations are activated when they receive an event or by
-``TSContSchedule`` (which schedules a continuation to receive an event).
+``TSContScheduleOnPool`` (which schedules a continuation to receive an event).
 Continuations might receive an event because:
 
 -  Your plugin calls ``TSContCall``
diff --git a/doc/developer-guide/plugins/continuations/index.en.rst b/doc/developer-guide/plugins/continuations/index.en.rst
index 94bd132..41722b1 100644
--- a/doc/developer-guide/plugins/continuations/index.en.rst
+++ b/doc/developer-guide/plugins/continuations/index.en.rst
@@ -54,7 +54,7 @@
    transactions uses ``TSContDataSet/Get``
 
 -  uses ``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, or
-   ``TSContSchedule`` APIs
+   ``TSContScheduleOnPool`` APIs
 
 Before being activated, a caller must grab the continuation's mutex.
 This requirement makes it possible for a continuation's handler function
diff --git a/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst b/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst
index b609cd3..8b79deb 100644
--- a/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst
+++ b/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst
@@ -95,7 +95,6 @@
 :macro:`TS_EVENT_CACHE_LOOKUP_COMPLETE`       :macro:`TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK`  :type:`TSHttpTxn`
 :macro:`TS_EVENT_IMMEDIATE`                   :func:`TSVConnClose`
                                               :func:`TSVIOReenable`
-                                              :func:`TSContSchedule`
                                               :func:`TSContScheduleOnPool`
                                               :func:`TSContScheduleOnThread`
 :macro:`TS_EVENT_IMMEDIATE`                   :macro:`TS_HTTP_REQUEST_TRANSFORM_HOOK`
@@ -113,8 +112,7 @@
                                               :func:`TSHttpTxnServerIntercept`
                                               :func:`TSHttpTxnIntercept`
 :macro:`TS_EVENT_HOST_LOOKUP`                 :func:`TSHostLookup`                         :type:`TSHostLookupResult`
-:macro:`TS_EVENT_TIMEOUT`                     :func:`TSContSchedule`
-                                              :func:`TSContScheduleOnPool`
+:macro:`TS_EVENT_TIMEOUT`                     :func:`TSContScheduleOnPool`
                                               :func:`TSContScheduleOnThread`
 :macro:`TS_EVENT_ERROR`
 :macro:`TS_EVENT_VCONN_READ_READY`            :func:`TSVConnRead`                          :type:`TSVIO`
@@ -137,7 +135,6 @@
 -  :func:`TSContDataSet`
 -  :func:`TSContDestroy`
 -  :func:`TSContMutexGet`
--  :func:`TSContSchedule`
 -  :func:`TSContScheduleOnPool`
 -  :func:`TSContScheduleOnThread`
 
diff --git a/doc/developer-guide/plugins/http-transformations/index.en.rst b/doc/developer-guide/plugins/http-transformations/index.en.rst
index 6e057dc..de75c6b 100644
--- a/doc/developer-guide/plugins/http-transformations/index.en.rst
+++ b/doc/developer-guide/plugins/http-transformations/index.en.rst
@@ -168,9 +168,10 @@
 Transaction Data Sink
 ~~~~~~~~~~~~~~~~~~~~~
 
-The hook `TS_HTTP_RESPONSE_CLIENT_HOOK` is a hook that supports a special type of transformation, one with only input and no output.
-Although the transformation doesn't provide data back to Traffic Server it can do anything else with the data, such as writing it
-to another output device or process. It must, however, consume all the data for the transaction. There are two primary use cases.
+The `TS_HTTP_REQUEST_CLIENT_HOOK` and `TS_HTTP_RESPONSE_CLIENT_HOOK` hooks supports a special type of transformation, one with only request or
+response body input and no output.  Although the transformation doesn't provide data back to Traffic Server they can do anything else with the
+data, such as writing it to another output device or process. It must, however, consume all the data for the transaction. There are two primary
+use cases.
 
 #. Tap in to the transaction to provide the data for external processing.
 #. Maintain the transaction.
diff --git a/doc/locale/ja/LC_MESSAGES/admin-guide/monitoring/logging/log-formats.en.po b/doc/locale/ja/LC_MESSAGES/admin-guide/monitoring/logging/log-formats.en.po
index 0c3ae85..9967d78 100644
--- a/doc/locale/ja/LC_MESSAGES/admin-guide/monitoring/logging/log-formats.en.po
+++ b/doc/locale/ja/LC_MESSAGES/admin-guide/monitoring/logging/log-formats.en.po
@@ -988,11 +988,6 @@
 "他のものです。( ``cqtx`` のサブセット )"
 
 #: ../../../admin-guide/monitoring/logging/log-formats.en.rst:403
-#, fuzzy
-msgid "``cqhv``"
-msgstr "``cqhl``"
-
-#: ../../../admin-guide/monitoring/logging/log-formats.en.rst:403
 msgid "The client request HTTP version."
 msgstr "クライアントリクエストの HTTP バージョン。"
 
@@ -2463,9 +2458,6 @@
 #~ msgid "chp"
 #~ msgstr "chp"
 
-#~ msgid "cqhv"
-#~ msgstr "cqhv"
-
 #~ msgid "cqpv"
 #~ msgstr "cqpv"
 
diff --git a/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/index.en.po b/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/index.en.po
index 187dd3e..c583db9 100644
--- a/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/index.en.po
+++ b/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/index.en.po
@@ -280,14 +280,6 @@
 msgid "MP4 streaming media."
 msgstr ""
 
-#: ../../../admin-guide/plugins/index.en.rst:177
-msgid ":doc:`MySQL Remap <mysql_remap.en>`"
-msgstr ""
-
-#: ../../../admin-guide/plugins/index.en.rst:177
-msgid "Allows dynamic remaps from a MySQL database."
-msgstr ""
-
 #: ../../../admin-guide/plugins/index.en.rst:180
 msgid ":doc:`Regex Revalidate <regex_revalidate.en>`"
 msgstr ""
diff --git a/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/mysql_remap.en.po b/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/mysql_remap.en.po
deleted file mode 100644
index 4bbd60e..0000000
--- a/doc/locale/ja/LC_MESSAGES/admin-guide/plugins/mysql_remap.en.po
+++ /dev/null
@@ -1,125 +0,0 @@
-# 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.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: Apache Traffic Server 6.2\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-01-02 21:32+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: ja_JP\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.1.1\n"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:57
-msgid "Configuration"
-msgstr "設定"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:4
-msgid "MySQL Remap Plugin"
-msgstr "MySQL リマッププラグイン"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:28
-msgid "The generic proxying setup is the following::"
-msgstr "一般的なプロキシーのセットアップは次のとおりです ::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:24
-msgid ""
-"This is a basic plugin for doing dynamic \"remaps\" from a database. It "
-"essentially rewrites the incoming request's Host header / origin server "
-"connection to one retrieved from a database."
-msgstr ""
-"これはデータベースをもとに動的な\"リマップ\"を行うための基本的なプラグインで"
-"す。本質的には届いたリクエストの Host ヘッダーを書き換えます / オリジンサー"
-"バーの接続をデータベースから取得したものに。"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:85
-msgid "And resolve any errors or warnings displayed."
-msgstr "そして表示されたエラーや警告を解決してください。"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:37
-msgid "Ends up requesting ``http://original.host.com/path/to/something``"
-msgstr ""
-"最終的には ``http://original.host.com/path/to/something`` にリクエストしま"
-"す。"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:48
-msgid "Installation"
-msgstr "インストール"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:42
-msgid ""
-"We have benchmarked the plugin with ab at about 9200 requests/sec (1.7k "
-"object) on a commodity hardware with a local setup of both, MySQL and "
-"Traffic Server local. Real performance is likely to be substantially "
-"higher, up to the MySQL's max queries / second."
-msgstr ""
-"私たちは ab でこのプラグインのベンチマークを行い、汎用のハードウェアに "
-"MySQL と Traffic Server をどちらもローカルにセットアップした環境で 約 9200 "
-"リクエスト / 秒 (1.7k オブジェクト ) となりました。実際のパフォーマンスは十"
-"分に高く、MySQL の最大クエリー / 秒次第です。"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:59
-msgid "Import the default schema to a database you create::"
-msgstr "作成したデータベースへのデフォルトスキーマを取り込みます ::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:72
-msgid "The INI file should contain the following values::"
-msgstr "INI ファイルは次の値を含むべきです ::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:50
-msgid "This plugin is only built if the configure option ::"
-msgstr "このプラグインはビルド時の設定オプションに ::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:81
-msgid "To debug errors, start trafficserver manually using::"
-msgstr ""
-"エラーをデバッグするために、Traffic Server を手動で次のように開始してくださ"
-"い::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:39
-msgid ""
-"With this plugin enabled, you can easily change that to anywhere you "
-"desire. Imagine the many possibilities...."
-msgstr ""
-"このプラグインを有効化すると、どこでも望んだ場所に簡単に変更できます。多くの"
-"可能性を想像してください...."
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:67
-msgid ""
-"Traffic Server plugin configuration is done inside a global configuration "
-"file: ``/etc/trafficserver/plugin.config``::"
-msgstr ""
-"Traffic Server プラグイン設定はグローバル設定ファイル :file: ``/etc/"
-"trafficserver/plugin.config`` で完了します ::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:32
-msgid "Without the plugin a request like::"
-msgstr "このプラグインを使わない場合のリクエストはこの様になります ::"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:64
-msgid ""
-"insert some interesting values in mysql_remap.hostname & mysql_remap.map"
-msgstr "mysql_remap.hostname と mysql_remap.map に興味深い値を入れます"
-
-#: ../../admin-guide/plugins/mysql_remap.en.rst:54
-msgid "is given at build time."
-msgstr "が与えられている場合にのみビルドされます。"
diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po
similarity index 81%
rename from doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po
rename to doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po
index e35760b..d41276f 100644
--- a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po
+++ b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po
@@ -29,18 +29,18 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 2.1.1\n"
 
-#: ../../developer-guide/api/functions/TSContSchedule.en.rst:32
+#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:32
 msgid "Description"
 msgstr "解説"
 
-#: ../../developer-guide/api/functions/TSContSchedule.en.rst:25
+#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:25
 msgid "Synopsis"
 msgstr "概要"
 
-#: ../../developer-guide/api/functions/TSContSchedule.en.rst:22
-msgid "TSContSchedule"
+#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:22
+msgid "TSContScheduleOnPool"
 msgstr ""
 
-#: ../../developer-guide/api/functions/TSContSchedule.en.rst:27
+#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:27
 msgid "`#include <ts/ts.h>`"
 msgstr "`#include <ts/ts.h>`"
diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po
index dc1ec75..c6a6187 100644
--- a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po
+++ b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po
@@ -290,10 +290,6 @@
 msgid ":ts:cv:`proxy.config.http.down_server.cache_time`"
 msgstr ""
 
-#: ../../../developer-guide/api/functions/TSHttpOverridableConfig.en.rst:119
-msgid ":ts:cv:`proxy.config.http.down_server.abort_threshold`"
-msgstr ""
-
 #: ../../../developer-guide/api/functions/TSHttpOverridableConfig.en.rst:120
 msgid ":ts:cv:`proxy.config.http.cache.fuzz.time`"
 msgstr ""
diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po
index 5e8bbaa..928db61 100644
--- a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po
+++ b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po
@@ -48,10 +48,10 @@
 #: ../../developer-guide/plugins/continuations/activating-continuations.en.rst:25
 msgid ""
 "Continuations are activated when they receive an event or by "
-"``TSContSchedule`` (which schedules a continuation to receive an event). "
+"``TSContScheduleOnPool`` (which schedules a continuation to receive an event). "
 "Continuations might receive an event because:"
 msgstr ""
-"継続は、イベントを受け取った際か ``TSContSchedule`` (イベントを受け取るため"
+"継続は、イベントを受け取った際か ``TSContScheduleOnPool`` (イベントを受け取るため"
 "継続をスケジュールする関数)によってアクティベートされます。下記の理由により"
 "継続がイベントを受け取る可能性があります。 :"
 
diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po
index b2595d9..b51a939 100644
--- a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po
+++ b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po
@@ -220,8 +220,8 @@
 
 #: ../../developer-guide/plugins/continuations/index.en.rst:56
 msgid ""
-"uses ``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, or ``TSContSchedule`` "
+"uses ``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, or ``TSContScheduleOnPool`` "
 "APIs"
 msgstr ""
-"``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, もしくは ``TSContSchedule`` "
+"``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, もしくは ``TSContScheduleOnPool`` "
 "API を使用する。"
diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po
index bdc58ce..df4b712 100644
--- a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po
+++ b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po
@@ -229,8 +229,8 @@
 msgstr ":data:`TS_EVENT_IMMEDIATE`"
 
 #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:90
-msgid ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContSchedule`"
-msgstr ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContSchedule`"
+msgid ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContScheduleOnPool`"
+msgstr ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContScheduleOnPool`"
 
 #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:93
 msgid ":data:`TS_HTTP_REQUEST_TRANSFORM_HOOK`"
@@ -329,8 +329,8 @@
 
 #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:108
 #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:130
-msgid ":func:`TSContSchedule`"
-msgstr ":func:`TSContSchedule`"
+msgid ":func:`TSContScheduleOnPool`"
+msgstr ":func:`TSContScheduleOnPool`"
 
 #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:109
 msgid ":data:`TS_EVENT_ERROR`"
diff --git a/doc/manpages.py b/doc/manpages.py
index 6664499..6ef5e09 100644
--- a/doc/manpages.py
+++ b/doc/manpages.py
@@ -35,8 +35,6 @@
         u'Traffic Server log spooler', None, '8'),
     ('appendices/command-line/traffic_logstats.en', 'traffic_logstats',
         u'Traffic Server analyzer', None, '8'),
-    ('appendices/command-line/traffic_manager.en', 'traffic_manager',
-        u'Traffic Server process manager', None, '8'),
     ('appendices/command-line/traffic_server.en', 'traffic_server',
         u'Traffic Server', None, '8'),
     ('appendices/command-line/traffic_top.en', 'traffic_top',
diff --git a/doc/release-notes/upgrading.en.rst b/doc/release-notes/upgrading.en.rst
index 5692087..edaab81 100644
--- a/doc/release-notes/upgrading.en.rst
+++ b/doc/release-notes/upgrading.en.rst
@@ -31,6 +31,24 @@
 version of ATS. Deprecated features should be avoided, with the expectation that they will be
 removed in the next major release of ATS.
 
+* Removed TS API
+
+  * TSHttpTxnArgSet
+  * TSHttpTxnArgGet
+  * TSHttpSsnArgSet
+  * TSHttpSsnArgGet
+  * TSVConnArgSet
+  * TSVConnArgGet
+  * TSHttpTxnArgIndexReserve
+  * TSHttpTxnArgIndexNameLookup
+  * TSHttpTxnArgIndexLookup
+  * TSHttpSsnArgIndexReserve
+  * TSHttpSsnArgIndexNameLookup
+  * TSHttpSsnArgIndexLookup
+  * TSVConnArgIndexReserve
+  * TSVConnArgIndexNameLookup
+  * TSVConnArgIndexLookup
+
 
 API Changes
 -----------
diff --git a/doc/release-notes/whats-new.en.rst b/doc/release-notes/whats-new.en.rst
index 0500df4..eb641e6 100644
--- a/doc/release-notes/whats-new.en.rst
+++ b/doc/release-notes/whats-new.en.rst
@@ -35,6 +35,12 @@
 New or modified Configurations
 ------------------------------
 
+Combined Connect Timeouts
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The configuration settings :ts:cv: `proxy.config.http.parent_proxy.connect_attempts_timeout` and :ts:cv: `proxy.config.http.post_connect_attempts_timeout` have been removed.
+All connect timeouts are controlled by :ts:cv: `proxy.config.http.connect_attempts_timeout`.
+
 Logging and Metrics
 -------------------
 
diff --git a/doc/uml/JsonRPCManager.uml b/doc/uml/JsonRPCManager.uml
new file mode 100644
index 0000000..ac1e3b1
--- /dev/null
+++ b/doc/uml/JsonRPCManager.uml
@@ -0,0 +1,32 @@
+' Licensed 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.
+@startuml
+class JsonRPCManager {
+add_handler(name, Func)
+add_notification_handler(name, Func)
+rsponse handle_call(request)
+JsonRPCManager instance()
+}
+note left: Singleton class.
+
+class Dispatcher {
+    _handlers: std::unordered_map<std::string, InternalHandler>
+}
+
+class InternalHandler {
+    _func: std::variant<std::monostate, Notification, Method, PluginMethod>
+}
+
+class FunctionWrapper {
+    callback: std::function
+}
+
+JsonRPCManager *-- Dispatcher
+note right: Class that knows how to call each callback handler. Storage class.
+Dispatcher *-- InternalHandler
+InternalHandler *-- FunctionWrapper
+@enduml
\ No newline at end of file
diff --git a/doc/uml/traffic_ctl-class-diagram.uml b/doc/uml/traffic_ctl-class-diagram.uml
new file mode 100644
index 0000000..a484837
--- /dev/null
+++ b/doc/uml/traffic_ctl-class-diagram.uml
@@ -0,0 +1,55 @@
+' Licensed 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.
+@startuml
+class traffic_ctl {
+
+command :std::shared_ptr<CtrlCommand>
+parser: ArgParser
+}
+note left: ArgParser Invoker
+class CtrlCommand {
+virtual void execute()
+_printer: std::unique_ptr<BasePrinter>
+}
+
+class DirectRPCCommand {
+void execute()
+}
+class RecordCommand {
+void execute()
+}
+class XCommand {
+void execute()
+}
+
+
+traffic_ctl -|> CtrlCommand
+CtrlCommand <|-- RecordCommand
+CtrlCommand <|-- DirectRPCCommand
+CtrlCommand <|-- XCommand
+
+abstract BasePrinter {
+void write_output(JSONRPCResponse)
+virtual void write_output(std::string_view)
+}
+CtrlCommand *-- BasePrinter
+
+class XPrinter{
+void write_output(JSONRPCResponse)
+}
+class DiffConfigPrinter{
+void write_output(JSONRPCResponse)
+}
+
+class RecordPrinter{
+void write_output(JSONRPCResponse)
+}
+
+BasePrinter <|-- DiffConfigPrinter
+BasePrinter <|-- RecordPrinter
+BasePrinter <|-- XPrinter
+@enduml
diff --git a/example/plugins/c-api/request_buffer/request_buffer.c b/example/plugins/c-api/request_buffer/request_buffer.c
index 69ab0c6..2214cda 100644
--- a/example/plugins/c-api/request_buffer/request_buffer.c
+++ b/example/plugins/c-api/request_buffer/request_buffer.c
@@ -67,7 +67,7 @@
 {
   TSDebug(PLUGIN_NAME, "request_buffer_plugin starting, event[%d]", event);
   TSHttpTxn txnp = (TSHttpTxn)(edata);
-  if (event == TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE) {
+  if (event == TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE) {
     int len    = 0;
     char *body = request_body_get(txnp, &len);
     TSDebug(PLUGIN_NAME, "request_buffer_plugin gets the request body with length[%d]", len);
diff --git a/example/plugins/c-api/txn_data_sink/txn_data_sink.cc b/example/plugins/c-api/txn_data_sink/txn_data_sink.cc
index 2be7d61..af4817c 100644
--- a/example/plugins/c-api/txn_data_sink/txn_data_sink.cc
+++ b/example/plugins/c-api/txn_data_sink/txn_data_sink.cc
@@ -1,6 +1,6 @@
 /** @file
 
-  Example plugin for using TS_HTTP_RESPONSE_CLIENT_HOOK.
+  Example plugin for using the TS_HTTP_REQUEST_CLIENT_HOOK and TS_HTTP_RESPONSE_CLIENT_HOOK hooks.
 
   This example is used to maintain the transaction and thence the connection to the origin server for the full
   transaction, even if the user agent aborts. This is useful in cases where there are other reasons to complete
@@ -37,8 +37,11 @@
 {
 constexpr char const *PLUGIN_NAME = "txn_data_sink";
 
-/** Activate the data sink if this header field is present in the request. */
-std::string_view FLAG_HEADER_FIELD = "TS-Agent";
+/** The flag for activating response body data sink for a transaction. */
+std::string_view FLAG_DUMP_RESPONSE_BODY = "X-Dump-Response";
+
+/** The flag for activating request body data sink for a transaction. */
+std::string_view FLAG_DUMP_REQUEST_BODY = "X-Dump-Request";
 
 /** The sink data for a transaction. */
 struct SinkData {
@@ -49,14 +52,36 @@
    * interact with the body as a stream rather than buffering the entire body
    * for each transaction.
    */
-  std::string body_bytes;
+  std::string response_body_bytes;
+
+  /** The bytes for the request body streamed in from the sink.
+   *
+   * @note This example plugin buffers the body which is useful for the
+   * associated Autest. In most production scenarios the user will want to
+   * interact with the body as a stream rather than buffering the entire body
+   * for each transaction.
+   */
+  std::string request_body_bytes;
 };
 
-// This serves to consume all the data that arrives. If it's not consumed the tunnel gets stalled
-// and the transaction doesn't complete. Other things could be done with the data, accessible via
-// the IO buffer @a reader, such as writing it to disk to make an externally accessible copy.
+/** A flag to request that response body bytes be sinked. */
+constexpr bool SINK_RESPONSE_BODY = true;
+
+/** A flag to request that request body bytes be sinked. */
+constexpr bool SINK_REQUEST_BODY = false;
+
+/** This serves to consume all the data that arrives in the VIO.
+ *
+ * Note that if any data is not consumed then the tunnel gets stalled and the
+ * transaction doesn't complete. Various things can be done with the data,
+ * accessible via the IO buffer @a reader, such as writing it to disk in order
+ * to make an externally accessible copy.
+ *
+ * @param[in] sync_response_body: Indicates whether response body bytes should
+ * be consumed.
+ */
 int
-client_reader(TSCont contp, TSEvent event, void *edata)
+body_reader_helper(TSCont contp, TSEvent event, bool sync_response_body)
 {
   SinkData *data = static_cast<SinkData *>(TSContDataGet(contp));
 
@@ -74,6 +99,8 @@
     TSContDataSet(contp, data);
   }
 
+  std::string &body_bytes = sync_response_body ? data->response_body_bytes : data->request_body_bytes;
+
   switch (event) {
   case TS_EVENT_ERROR:
     TSDebug(PLUGIN_NAME, "Error event");
@@ -90,9 +117,9 @@
       TSIOBufferReader reader = TSVIOReaderGet(input_vio);
       size_t const n          = TSIOBufferReaderAvail(reader);
       if (n > 0) {
-        auto const offset = data->body_bytes.size();
-        data->body_bytes.resize(offset + n);
-        TSIOBufferReaderCopy(reader, data->body_bytes.data() + offset, n);
+        auto const offset = body_bytes.size();
+        body_bytes.resize(offset + n);
+        TSIOBufferReaderCopy(reader, body_bytes.data() + offset, n);
 
         TSIOBufferReaderConsume(reader, n);
         TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + n);
@@ -102,11 +129,11 @@
         // Signal that we can accept more data.
         TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
       } else {
-        TSDebug(PLUGIN_NAME, "Consumed the following body: \"%.*s\"", (int)data->body_bytes.size(), data->body_bytes.data());
+        TSDebug(PLUGIN_NAME, "Consumed the following body: \"%.*s\"", static_cast<int>(body_bytes.size()), body_bytes.data());
         TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
       }
     } else { // The buffer is gone so we're done.
-      TSDebug(PLUGIN_NAME, "upstream buffer disappeared - %zd bytes", data->body_bytes.size());
+      TSDebug(PLUGIN_NAME, "upstream buffer disappeared - %zd bytes", body_bytes.size());
     }
     break;
   default:
@@ -117,43 +144,90 @@
   return 0;
 }
 
+/** The handler for transaction data sink for response bodies. */
 int
-enable_agent_check(TSHttpTxn txnp)
+response_body_reader(TSCont contp, TSEvent event, void *edata)
 {
-  TSMBuffer req_buf;
-  TSMLoc req_loc;
-  int zret = 0;
+  return body_reader_helper(contp, event, SINK_RESPONSE_BODY);
+}
 
-  // Enable the sink agent if the header is present.
+/** The handler for transaction data sink for request bodies. */
+int
+request_body_reader(TSCont contp, TSEvent event, void *edata)
+{
+  return body_reader_helper(contp, event, SINK_REQUEST_BODY);
+}
+
+/** A helper function for common logic between request_sink_requested and
+ * response_sink_requested. */
+bool
+sink_requested_helper(TSHttpTxn txnp, std::string_view header)
+{
+  TSMLoc field      = nullptr;
+  TSMBuffer req_buf = nullptr;
+  TSMLoc req_loc    = nullptr;
   if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &req_buf, &req_loc)) {
-    TSMLoc agent_field = TSMimeHdrFieldFind(req_buf, req_loc, FLAG_HEADER_FIELD.data(), FLAG_HEADER_FIELD.length());
-    zret               = nullptr == agent_field ? 0 : 1;
+    field = TSMimeHdrFieldFind(req_buf, req_loc, header.data(), header.length());
   }
 
-  return zret;
+  return field != nullptr;
 }
 
-void
-client_add(TSHttpTxn txnp)
+/** Determine whether the headers enable request body sink.
+ *
+ * Inspect the given request headers for the flag that enables request body
+ * sink.
+ *
+ * @param[in] txnp The transaction with the request headers to search for the
+ * header that enables request body sink.
+ *
+ * @return True if the headers enable request body sink, false otherwise.
+ */
+bool
+request_sink_requested(TSHttpTxn txnp)
 {
-  // We use @c TSTransformCreate because ATS sees this the same as a transform, but with only
-  // the input side hooked up and not the output side. Data flows from ATS in to the reader
-  // but not back out to ATS. From the plugin point of view the input data is provided exactly
-  // as it is with a transform.
-  TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_CLIENT_HOOK, TSTransformCreate(client_reader, txnp));
+  return sink_requested_helper(txnp, FLAG_DUMP_REQUEST_BODY);
 }
 
+/** Determine whether the headers enable response body sink.
+ *
+ * Inspect the given response headers for the flag that enables response body
+ * sink.
+ *
+ * @param[in] txnp The transaction with the response headers to search for the
+ * header that enables response body sink.
+ *
+ * @return True if the headers enable response body sink, false otherwise.
+ */
+bool
+response_sink_requested(TSHttpTxn txnp)
+{
+  return sink_requested_helper(txnp, FLAG_DUMP_RESPONSE_BODY);
+}
+
+/** Implements the handler for inspecting the request header bytes and enabling
+ * transaction data sink if X-Dump-Request or X-Dump-Response flags are used.
+ */
 int
 main_hook(TSCont contp, TSEvent event, void *edata)
 {
-  TSHttpTxn txnp = (TSHttpTxn)edata;
+  TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
 
-  TSDebug(PLUGIN_NAME, "Checking transaction");
+  TSDebug(PLUGIN_NAME, "Checking transaction for any flags to enable transaction data sink.");
   switch (event) {
-  case TS_EVENT_HTTP_READ_RESPONSE_HDR:
-    if (enable_agent_check(txnp)) {
-      TSDebug(PLUGIN_NAME, "Adding data sink to transaction");
-      client_add(txnp);
+  case TS_EVENT_HTTP_READ_REQUEST_HDR:
+    /// We use @c TSTransformCreate because ATS sees this the same as a
+    /// transform, but with only the input side hooked up and not the output
+    /// side. Data flows from ATS in to the reader but not back out to ATS.
+    /// From the plugin point of view the input data is provided exactly as it
+    /// is with a transform.
+    if (response_sink_requested(txnp)) {
+      TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_CLIENT_HOOK, TSTransformCreate(response_body_reader, txnp));
+      TSDebug(PLUGIN_NAME, "Adding response data sink to transaction");
+    }
+    if (request_sink_requested(txnp)) {
+      TSHttpTxnHookAdd(txnp, TS_HTTP_REQUEST_CLIENT_HOOK, TSTransformCreate(request_body_reader, txnp));
+      TSDebug(PLUGIN_NAME, "Adding request data sink to transaction");
     }
     break;
   default:
@@ -179,6 +253,6 @@
     return;
   }
 
-  TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(main_hook, nullptr));
+  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(main_hook, nullptr));
   return;
 }
diff --git a/include/records/I_RecAlarms.h b/include/records/I_RecAlarms.h
deleted file mode 100644
index f4843c9..0000000
--- a/include/records/I_RecAlarms.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/** @file
-
-  Public REC_ALARM defines
-
-  @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.
- */
-
-#pragma once
-
-// copy from mgmt/Alarms.h
-#define REC_ALARM_UNDEFINED 0
-
-#define REC_ALARM_PROXY_PROCESS_DIED 1
-#define REC_ALARM_PROXY_PROCESS_BORN 2
-#define REC_ALARM_PROXY_CONFIG_ERROR 3
-#define REC_ALARM_PROXY_SYSTEM_ERROR 4
-#define REC_ALARM_PROXY_CACHE_ERROR 5
-#define REC_ALARM_PROXY_CACHE_WARNING 6
-#define REC_ALARM_PROXY_LOGGING_ERROR 7
-#define REC_ALARM_PROXY_LOGGING_WARNING 8
-#define REC_ALARM_CONFIG_UPDATE_FAILED 9
diff --git a/include/records/I_RecCore.h b/include/records/I_RecCore.h
index 061ebb8..378e47c 100644
--- a/include/records/I_RecCore.h
+++ b/include/records/I_RecCore.h
@@ -28,10 +28,7 @@
 #include "tscore/Diags.h"
 
 #include "I_RecDefs.h"
-#include "I_RecAlarms.h"
-#include "I_RecSignals.h"
-#include "I_RecEvents.h"
-#include "tscpp/util/MemSpan.h"
+#include "swoc/MemSpan.h"
 
 struct RecRecord;
 
@@ -176,22 +173,6 @@
 RecErrT RecGetRecordAccessType(const char *name, RecAccessT *secure, bool lock = true);
 RecErrT RecSetRecordAccessType(const char *name, RecAccessT secure, bool lock = true);
 
-//------------------------------------------------------------------------
-// Signal and Alarms
-//------------------------------------------------------------------------
-
-// RecSignalManager always sends a management signal up to traffic_manager.
-void RecSignalManager(int id, const char *, size_t);
-
-static inline void
-RecSignalManager(int id, const char *str)
-{
-  RecSignalManager(id, str, strlen(str) + 1);
-}
-
-// Format a message, and send it to the manager and to the Warning diagnostic.
-void RecSignalWarning(int sig, const char *fmt, ...) TS_PRINTFLIKE(2, 3);
-
 /// Generate a warning if any configuration name/value is not registered.
 void RecConfigWarnIfUnregistered();
 
@@ -300,9 +281,3 @@
 // Set RecRecord attributes
 //------------------------------------------------------------------------
 RecErrT RecSetSyncRequired(char *name, bool lock = true);
-
-//------------------------------------------------------------------------
-// Manager Callback
-//------------------------------------------------------------------------
-using RecManagerCb = std::function<void(ts::MemSpan<void>)>;
-int RecRegisterManagerCb(int _signal, RecManagerCb const &_fn);
diff --git a/include/records/I_RecDefs.h b/include/records/I_RecDefs.h
index cfe9206..5baf5d9 100644
--- a/include/records/I_RecDefs.h
+++ b/include/records/I_RecDefs.h
@@ -100,7 +100,7 @@
   RECU_NULL,       // default: don't know the behavior
   RECU_DYNAMIC,    // config can be updated dynamically w/ "traffic_ctl config reload"
   RECU_RESTART_TS, // config requires TS to be restarted to take effect
-  RECU_RESTART_TM, // config requires TM/TS to be restarted to take effect
+  RECU_RESTART_TM, // deprecated
 };
 
 enum RecCheckT {
@@ -125,7 +125,7 @@
   RECM_NULL,
   RECM_CLIENT,
   RECM_SERVER,
-  RECM_STAND_ALONE,
+  RECM_STAND_ALONE, ///< The only option now as traffic_manager is no longer supported.
 };
 
 enum RecAccessT {
diff --git a/include/records/I_RecEvents.h b/include/records/I_RecEvents.h
deleted file mode 100644
index 3e3c476..0000000
--- a/include/records/I_RecEvents.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/** @file
-
-  Public REC_EVENT defines
-
-  @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.
- */
-
-#pragma once
-
-// copy from mgmt/BaseManager.h
-#define REC_EVENT_SYNC_KEY 10000
-#define REC_EVENT_SHUTDOWN 10001
-#define REC_EVENT_RESTART 10002
-#define REC_EVENT_BOUNCE 10003
-#define REC_EVENT_CLEAR_STATS 10004
-#define REC_EVENT_CONFIG_FILE_UPDATE 10005
-#define REC_EVENT_PLUGIN_CONFIG_UPDATE 10006
-#define REC_EVENT_ROLL_LOG_FILES 10008
-#define REC_EVENT_LIBRECORDS 10009
-
-#define REC_EVENT_CACHE_DISK_CONTROL 10011
diff --git a/include/records/I_RecHttp.h b/include/records/I_RecHttp.h
index 36f8714..8dad83c 100644
--- a/include/records/I_RecHttp.h
+++ b/include/records/I_RecHttp.h
@@ -520,3 +520,31 @@
     This must be called before any proxy port parsing is done.
 */
 extern void ts_session_protocol_well_known_name_indices_init();
+
+/** Convert the comma separated ALPN protocol list to wire format.
+ *
+ * For the definition of wire format, see the NOTES section in the OpenSSL
+ * description of SSL_CTX_set_alpn_select_cb:
+ *
+ * https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_alpn_select_cb.html
+ *
+ * @param[in] protocols The comma separated list of protocols to convert to
+ *   wire format.
+ *
+ * @param[out] wire_format_buffer The output ALPN wire format string converted
+ *   from @a protocols. This is zero'd out if the conversion fails.
+ *
+ * @param[in,out] wire_format_buffer_len As an input, this is the size
+ *   allocated for @a wire_format_buffer. As an output, this is set to the final
+ *   size of @a wire_format_buffer after conversion. This is set to zero if the
+ *   conversion fails.
+ *
+ * @return True if the conversion was successful, false otherwise. Note that
+ * the wire format does not support an empty protocol list, therefore this
+ * function returns false if @a protocols is an empty string.
+ *
+ * TODO: ideally this would take a ts::TextView for @a protocols, but currently
+ * ts::TextView does not have a char* constructor while std::string_view does.
+ * Once that is added, this can be seemlessly switched to a ts::TextView.
+ */
+bool convert_alpn_to_wire_format(std::string_view protocols, unsigned char *wire_format_buffer, int &wire_format_buffer_len);
diff --git a/include/records/I_RecSignals.h b/include/records/I_RecSignals.h
deleted file mode 100644
index 2f1d5ab..0000000
--- a/include/records/I_RecSignals.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/** @file
-
-  Public REC_SIGNAL defines
-
-  @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.
- */
-
-#pragma once
-
-// copy from mgmt/BaseManager.h
-#define REC_SIGNAL_PID 0
-
-#define REC_SIGNAL_PROXY_PROCESS_DIED 1
-#define REC_SIGNAL_PROXY_PROCESS_BORN 2
-#define REC_SIGNAL_CONFIG_ERROR 3
-#define REC_SIGNAL_SYSTEM_ERROR 4
-#define REC_SIGNAL_CACHE_ERROR 5
-#define REC_SIGNAL_CACHE_WARNING 6
-#define REC_SIGNAL_LOGGING_ERROR 7
-#define REC_SIGNAL_LOGGING_WARNING 8
-#define REC_SIGNAL_PLUGIN_SET_CONFIG 9
-
-// This are additional on top of the ones defined in Alarms.h. Que?
-#define REC_SIGNAL_LIBRECORDS 10
-#define REC_SIGNAL_CONFIG_FILE_CHILD 11
diff --git a/include/records/P_RecCore.h b/include/records/P_RecCore.h
index 5840f5d..9613222 100644
--- a/include/records/P_RecCore.h
+++ b/include/records/P_RecCore.h
@@ -84,10 +84,6 @@
 //-------------------------------------------------------------------------
 
 bool i_am_the_record_owner(RecT rec_type);
-RecErrT send_push_message();
-RecErrT send_pull_message(RecMessageT msg_type);
-RecErrT send_register_message(RecRecord *record);
-RecErrT recv_message_cb(RecMessage *msg, RecMessageT msg_type, void *cookie);
 RecUpdateT RecExecConfigUpdateCbs(unsigned int update_required_type);
 
 void RecDumpRecordsHt(RecT rec_type = RECT_NULL);
diff --git a/include/records/P_RecLocal.h b/include/records/P_RecLocal.h
index e9caf32..1a83812 100644
--- a/include/records/P_RecLocal.h
+++ b/include/records/P_RecLocal.h
@@ -22,5 +22,3 @@
  */
 
 #pragma once
-
-#include "I_RecLocal.h"
diff --git a/include/records/P_RecMessage.h b/include/records/P_RecMessage.h
index 24930fe..8f4947a 100644
--- a/include/records/P_RecMessage.h
+++ b/include/records/P_RecMessage.h
@@ -24,7 +24,7 @@
 #pragma once
 
 #include "P_RecDefs.h"
-#include "tscpp/util/MemSpan.h"
+#include "swoc/MemSpan.h"
 
 //-------------------------------------------------------------------------
 // Initialization
@@ -43,9 +43,7 @@
 int RecMessageUnmarshalFirst(RecMessage *msg, RecMessageItr *itr, RecRecord **record);
 int RecMessageUnmarshalNext(RecMessage *msg, RecMessageItr *itr, RecRecord **record);
 
-int RecMessageSend(RecMessage *msg);
 int RecMessageRegisterRecvCb(RecMessageRecvCb recv_cb, void *cookie);
-void RecMessageRecvThis(ts::MemSpan<void>);
 
 RecMessage *RecMessageReadFromDisk(const char *fpath);
 int RecMessageWriteToDisk(RecMessage *msg, const char *fpath);
diff --git a/include/shared/rpc/IPCSocketClient.h b/include/shared/rpc/IPCSocketClient.h
new file mode 100644
index 0000000..841f254
--- /dev/null
+++ b/include/shared/rpc/IPCSocketClient.h
@@ -0,0 +1,92 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <stdexcept>
+#include <chrono>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <tscore/BufferWriter.h>
+
+namespace shared::rpc
+{
+/// The goal of this class is abstract the Unix Socket implementation and provide a JSONRPC Node client for Tests and client's
+/// applications like traffic_ctl and traffic_top.
+/// To make the usage easy and more readable this class provides a chained API, so you can do this like this:
+///
+///    IPCSocketClient client;
+///    auto resp = client.connect().send(json).read();
+///
+/// There is also a @c RPCClient class which should be used unless you need some extra control of the socket client.
+///
+/// Error handling: Enclose this inside a try/catch because if any error is detected functions will throw.
+struct IPCSocketClient {
+  enum class ReadStatus { NO_ERROR = 0, BUFFER_FULL, STREAM_ERROR, UNKNOWN };
+  using self_reference = IPCSocketClient &;
+
+  IPCSocketClient(std::string path) : _path{std::move(path)} {}
+  IPCSocketClient() : _path{"/tmp/jsonrpc20.sock"} {}
+
+  ~IPCSocketClient() { this->disconnect(); }
+
+  /// Connect to the configured socket path.
+  self_reference connect();
+
+  /// Send all the passed string to the socket.
+  self_reference send(std::string_view data);
+
+  /// Read all the content from the socket till the passed buffer is full.
+  ReadStatus read_all(ts::FixedBufferWriter &bw);
+
+  /// Closes the socket.
+  void
+  disconnect()
+  {
+    this->close();
+  }
+
+  /// Close the socket.
+  void
+  close()
+  {
+    if (_sock > 0) {
+      ::close(_sock);
+      _sock = -1;
+    }
+  }
+
+  /// Test if the socket was closed or it wasn't initialized.
+  bool
+  is_closed() const
+  {
+    return (_sock < 0);
+  }
+
+protected:
+  std::string _path;
+  struct sockaddr_un _server;
+
+  int _sock{-1};
+};
+} // namespace shared::rpc
diff --git a/include/shared/rpc/README.md b/include/shared/rpc/README.md
new file mode 100644
index 0000000..f4b616c
--- /dev/null
+++ b/include/shared/rpc/README.md
@@ -0,0 +1,9 @@
+# JSONRPC 2.0 Client API utility definitions.
+
+All this definitions are meant to be used by clients of the JSONRPC node which
+are looking to interact with it in a different C++ application, like traffic_ctl
+and traffic_top.
+All this definitions under the shared::rpc namespace are a client lightweight
+version of the ones used internally by the JSONRPC node server/handlers, they
+should not be mixed with the ones defined in `mgmt/rpc/jsonrpc` which are for
+internal use only.
diff --git a/include/shared/rpc/RPCClient.h b/include/shared/rpc/RPCClient.h
new file mode 100644
index 0000000..5cdfea8
--- /dev/null
+++ b/include/shared/rpc/RPCClient.h
@@ -0,0 +1,107 @@
+/**
+  @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.
+*/
+#pragma once
+
+/// JSONRPC 2.0 RPC network client.
+
+#include <iostream>
+#include <string_view>
+
+#include <yaml-cpp/yaml.h>
+#include <tscore/I_Layout.h>
+#include <tscore/BufferWriter.h>
+
+#include "IPCSocketClient.h"
+#include "yaml_codecs.h"
+
+namespace shared::rpc
+{
+///
+/// @brief Wrapper class to interact with the RPC node. Do not use this internally, this is for client's applications only.
+///
+class RPCClient
+{
+  // Large buffer, as we may query a full list of records.
+  // TODO: should we add a parameter to increase the buffer? or maybe a record limit on the server's side?
+  static constexpr int BUFFER_SIZE{356000};
+
+public:
+  RPCClient() : _client(Layout::get()->runtimedir + "/jsonrpc20.sock") {}
+
+  /// @brief invoke the remote function using the passed jsonrpc message string.
+  /// This function will connect with the remote rpc node and send the passed json string. If you don't want to deal with the
+  /// endode/decode you can just call @c invoke(JSONRPCRequest const &req).
+  /// @throw runtime_error
+  std::string
+  invoke(std::string_view req)
+  {
+    std::string text; // for error messages.
+    ts::LocalBufferWriter<BUFFER_SIZE> bw;
+    try {
+      _client.connect();
+      if (!_client.is_closed()) {
+        _client.send(req);
+        switch (_client.read_all(bw)) {
+        case IPCSocketClient::ReadStatus::NO_ERROR: {
+          _client.disconnect();
+          return {bw.data(), bw.size()};
+        }
+        case IPCSocketClient::ReadStatus::BUFFER_FULL: {
+          throw std::runtime_error(
+            ts::bwprint(text, "Buffer full, not enough space to read the response. Buffer size: {}", BUFFER_SIZE));
+        } break;
+        default:
+          throw std::runtime_error("Something happened, we can't read the response");
+          break;
+        }
+      } else {
+        throw std::runtime_error(ts::bwprint(text, "Node seems not available: {}", std ::strerror(errno)));
+      }
+    } catch (std::exception const &ex) {
+      _client.disconnect();
+      throw std::runtime_error(ts::bwprint(text, "RPC Node Error: {}", ex.what()));
+    }
+
+    return {};
+  }
+
+  /// @brief Invoke the rpc node passing the JSONRPC objects.
+  /// This function will connect with the remote rpc node and send the passed objects which will be encoded and decoded using the
+  /// yamlcpp_json_emitter impl.
+  /// @note If you inherit from @c JSONRPCRequest make sure the base members are properly filled before calling this function, the
+  /// encode/decode will only deal with the @c JSONRPCRequest members, unless you pass your own codec class. By default @c
+  /// yamlcpp_json_emitter is used. If you pass your own Codecs, make sure you follow the yamlcpp_json_emitter API.
+  /// @throw runtime_error
+  /// @throw YAML::Exception
+  template <typename Codec = yamlcpp_json_emitter>
+  JSONRPCResponse
+  invoke(JSONRPCRequest const &req)
+  {
+    static_assert(internal::has_decode<Codec>::value || internal::has_encode<Codec>::value,
+                  "You need to implement encode/decode in your own codec impl.");
+    // We should add a static_assert and make sure encode/decode are part of Codec type.
+    auto const &reqStr = Codec::encode(req);
+    return Codec::decode(invoke(reqStr));
+  }
+
+private:
+  IPCSocketClient _client;
+};
+} // namespace shared::rpc
\ No newline at end of file
diff --git a/include/shared/rpc/RPCRequests.h b/include/shared/rpc/RPCRequests.h
new file mode 100644
index 0000000..7faaa3e
--- /dev/null
+++ b/include/shared/rpc/RPCRequests.h
@@ -0,0 +1,216 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <string>
+#include <variant>
+#include <yaml-cpp/yaml.h>
+#include <tscore/BufferWriter.h>
+#include <tscore/ink_uuid.h>
+
+/// JSONRPC 2.0 Client API utility definitions. Only client applications should use these definitions. Internal handlers should not
+/// use these definitions. Check @c mgmg2/rpc/jsonrpc/Defs.h instead
+
+/// @brief @c JSONRPC 2.0 message mapping classes.
+/// This is a very thin  API to deal with encoding/decoding jsonrpc 2.0 messages.
+/// More info can be found https://www.jsonrpc.org/specification
+namespace shared::rpc
+{
+struct JSONRPCRequest {
+  std::string jsonrpc{"2.0"}; //!< Always 2.0 as this is the only version that teh server supports.
+  std::string method;         //!< remote method name.
+  std::string id;             //!< optional, only needed for method calls.
+  YAML::Node params;          //!< This is defined by each remote API.
+
+  virtual ~JSONRPCRequest() {} ///< Virtual destructor required because virtual methods present.
+
+  virtual std::string
+  get_method() const
+  {
+    return this->method;
+  }
+};
+
+struct JSONRPCResponse {
+  std::string id;      //!< Always 2.0 as this is the only version that teh server supports.
+  std::string jsonrpc; //!< Always 2.0
+  YAML::Node result; //!< Server's response, this could be decoded by using the YAML::convert mechanism. This depends solely on the
+                     //!< server's data. Check docs and schemas.
+  YAML::Node error;  //!<  Server's error.
+
+  /// Handy function to check if the server sent any error
+  bool
+  is_error() const
+  {
+    return !error.IsNull();
+  }
+
+  YAML::Node fullMsg;
+};
+
+struct JSONRPCError {
+  int32_t code;        //!< High level error code.
+  std::string message; //!< High level message
+  // the following data is defined by TS, it will be a key/value pair.
+  std::vector<std::pair<int32_t, std::string>> data;
+  friend std::ostream &operator<<(std::ostream &os, const JSONRPCError &err);
+};
+
+/**
+ All of the following definitions have the main purpose to have a object style idiom when dealing with request and responses from/to
+ the JSONRPC server. This structures will then be used by the YAML codec implementation by using the YAML::convert style.
+*/
+
+///
+/// @brief Base Client JSONRPC client request.
+///
+/// This represents a base class that implements the basic jsonrpc 2.0 required fields. We use UUID as an id generator
+/// but this was an arbitrary choice, there is no conditions that forces to use this, any random id could work too.
+/// When inherit from this class the @c id  and the @c jsonrpc fields which are constant in all the request will be automatically
+/// generated.
+// TODO: fix this as id is optional.
+struct ClientRequest : JSONRPCRequest {
+  using super = JSONRPCRequest;
+  ClientRequest() { super::id = _idGen.getString(); }
+
+private:
+  struct IdGenerator {
+    IdGenerator() { _uuid.initialize(TS_UUID_V4); }
+    const char *
+    getString()
+    {
+      return _uuid.valid() ? _uuid.getString() : "fix.this.is.not.an.id";
+    }
+    ATSUuid _uuid;
+  };
+  IdGenerator _idGen;
+};
+
+/// @brief Class definition just to make clear that it will be a notification and no ID will be set.
+struct ClientRequestNotification : JSONRPCRequest {
+  ClientRequestNotification() {}
+};
+/**
+ * Specific JSONRPC request implementation should be placed here. All this definitions helps for readability and in particular
+ * to easily emit json(or yaml) from this definitions.
+ */
+
+//------------------------------------------------------------------------------------------------------------------------------------
+
+// handy definitions.
+static const std::vector<int> CONFIG_REC_TYPES = {1, 16};
+static const std::vector<int> METRIC_REC_TYPES = {2, 4, 32};
+static constexpr bool NOT_REGEX{false};
+static constexpr bool REGEX{true};
+
+///
+/// @brief Record lookup API helper class.
+///
+/// This utility class is used to encapsulate the basic data that contains a record lookup request.
+/// Requests that are meant to interact with the admin_lookup_records API should inherit from this class if a special treatment is
+/// needed. Otherwise use it directly.
+///
+struct RecordLookupRequest : ClientRequest {
+  using super = ClientRequest;
+  struct Params {
+    std::string recName;
+    bool isRegex{false};
+    std::vector<int> recTypes;
+  };
+  std::string
+  get_method() const override
+  {
+    return "admin_lookup_records";
+  }
+  template <typename... Args>
+  void
+  emplace_rec(Args &&... p)
+  {
+    super::params.push_back(Params{std::forward<Args>(p)...});
+  }
+};
+
+struct RecordLookUpResponse {
+  /// Response Records API  mapping utility classes.
+  /// This utility class is used to hold the decoded response.
+  struct RecordParamInfo {
+    std::string name;
+    int32_t type;
+    int32_t version;
+    bool registered;
+    int32_t rsb;
+    int32_t order;
+    int32_t rclass;
+    bool overridable;
+    std::string dataType;
+    std::string currentValue;
+    std::string defaultValue;
+
+    struct ConfigMeta {
+      int32_t accessType;
+      int32_t updateStatus;
+      int32_t updateType;
+      int32_t checkType;
+      int32_t source;
+      std::string checkExpr;
+    };
+    struct StatMeta {
+      int32_t persistType;
+    };
+    std::variant<ConfigMeta, StatMeta> meta;
+  };
+  /// Record request error mapping class.
+  struct RecordError {
+    std::string code;
+    std::string recordName;
+    std::string message; //!< optional.
+    friend std::ostream &operator<<(std::ostream &os, const RecordError &re);
+  };
+
+  std::vector<RecordParamInfo> recordList;
+  std::vector<RecordError> errorList;
+};
+
+//------------------------------------------------------------------------------------------------------------------------------------
+
+inline std::ostream &
+operator<<(std::ostream &os, const RecordLookUpResponse::RecordError &re)
+{
+  std::string text;
+  os << ts::bwprint(text, "{:16s}: {}\n", "Record Name ", re.recordName);
+  os << ts::bwprint(text, "{:16s}: {}\n", "Code", re.code);
+  if (!re.message.empty()) {
+    os << ts::bwprint(text, "{:16s}: {}\n", "Message", re.message);
+  }
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os, const JSONRPCError &err)
+{
+  os << "Server Error found:\n";
+  os << "[" << err.code << "] " << err.message << '\n';
+  for (auto &&[code, message] : err.data) {
+    os << "- [" << code << "] " << message << '\n';
+  }
+
+  return os;
+}
+} // namespace shared::rpc
diff --git a/include/shared/rpc/yaml_codecs.h b/include/shared/rpc/yaml_codecs.h
new file mode 100644
index 0000000..db230ee
--- /dev/null
+++ b/include/shared/rpc/yaml_codecs.h
@@ -0,0 +1,243 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <string_view>
+#include <yaml-cpp/yaml.h>
+
+#include "RPCRequests.h"
+
+/// JSONRPC 2.0 Client API request/response codecs only. If you need to define your own specific codecs they should then be defined
+/// in a different file, unless they are strongly related to the ones defined here.
+
+namespace helper
+{
+// For some fields, If we can't get the value, then just send the default/empty value. Let the
+// traffic_ctl display something.
+template <typename T>
+inline auto
+try_extract(YAML::Node const &node, const char *name, bool throwOnFail = false)
+{
+  try {
+    if (auto n = node[name]) {
+      return n.as<T>();
+    }
+  } catch (YAML::Exception const &ex) {
+    if (throwOnFail) {
+      throw ex;
+    }
+  }
+  return T{};
+}
+} // namespace helper
+/**
+ * YAML namespace. All json rpc request codecs can be placed here. It will read all the definitions from "requests.h"
+ * It's noted that there may be some duplicated with the rpc server implementation structures but as this is very simple idiom where
+ * we define the data as plain as possible and it's just used for printing purposes, No major harm on having them duplicated
+ */
+
+namespace YAML
+{
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::JSONRPCError> {
+  static bool
+  decode(Node const &node, shared::rpc::JSONRPCError &error)
+  {
+    error.code    = helper::try_extract<int32_t>(node, "code");
+    error.message = helper::try_extract<std::string>(node, "message");
+    if (auto data = node["data"]) {
+      for (auto &&err : data) {
+        error.data.emplace_back(helper::try_extract<int32_t>(err, "code"), helper::try_extract<std::string>(err, "message"));
+      }
+    }
+    return true;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::RecordLookUpResponse::RecordParamInfo::ConfigMeta> {
+  static bool
+  decode(Node const &node, shared::rpc::RecordLookUpResponse::RecordParamInfo::ConfigMeta &meta)
+  {
+    meta.accessType   = helper::try_extract<int32_t>(node, "access_type");
+    meta.updateStatus = helper::try_extract<int32_t>(node, "update_status");
+    meta.updateType   = helper::try_extract<int32_t>(node, "update_type");
+    meta.checkType    = helper::try_extract<int32_t>(node, "checktype");
+    meta.source       = helper::try_extract<int32_t>(node, "source");
+    meta.checkExpr    = helper::try_extract<std::string>(node, "check_expr");
+    return true;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::RecordLookUpResponse::RecordParamInfo::StatMeta> {
+  static bool
+  decode(Node const &node, shared::rpc::RecordLookUpResponse::RecordParamInfo::StatMeta &meta)
+  {
+    meta.persistType = helper::try_extract<int32_t>(node, "persist_type");
+    return true;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::RecordLookUpResponse::RecordParamInfo> {
+  static bool
+  decode(Node const &node, shared::rpc::RecordLookUpResponse::RecordParamInfo &info)
+  {
+    info.name         = helper::try_extract<std::string>(node, "record_name");
+    info.type         = helper::try_extract<int32_t>(node, "record_type");
+    info.version      = helper::try_extract<int32_t>(node, "version");
+    info.registered   = helper::try_extract<bool>(node, "registered");
+    info.rsb          = helper::try_extract<int32_t>(node, "raw_stat_block");
+    info.order        = helper::try_extract<int32_t>(node, "order");
+    info.rclass       = helper::try_extract<int32_t>(node, "record_class");
+    info.overridable  = helper::try_extract<bool>(node, "overridable");
+    info.dataType     = helper::try_extract<std::string>(node, "data_type");
+    info.currentValue = helper::try_extract<std::string>(node, "current_value");
+    info.defaultValue = helper::try_extract<std::string>(node, "default_value");
+    try {
+      if (auto n = node["config_meta"]) {
+        info.meta = n.as<shared::rpc::RecordLookUpResponse::RecordParamInfo::ConfigMeta>();
+      } else if (auto n = node["stat_meta"]) {
+        info.meta = n.as<shared::rpc::RecordLookUpResponse::RecordParamInfo::StatMeta>();
+      }
+    } catch (Exception const &ex) {
+      return false;
+    }
+    return true;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::RecordLookUpResponse> {
+  static bool
+  decode(Node const &node, shared::rpc::RecordLookUpResponse &info)
+  {
+    try {
+      auto records = node["recordList"];
+      for (auto &&item : records) {
+        if (auto record = item["record"]) {
+          info.recordList.push_back(record.as<shared::rpc::RecordLookUpResponse::RecordParamInfo>());
+        }
+      }
+
+      auto errors = node["errorList"];
+      for (auto &&item : errors) {
+        info.errorList.push_back(item.as<shared::rpc::RecordLookUpResponse::RecordError>());
+      }
+    } catch (Exception const &ex) {
+      return false;
+    }
+    return true;
+  }
+};
+
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::RecordLookupRequest::Params> {
+  static Node
+  encode(shared::rpc::RecordLookupRequest::Params const &info)
+  {
+    Node record;
+    if (info.isRegex) {
+      record["record_name_regex"] = info.recName;
+    } else {
+      record["record_name"] = info.recName;
+    }
+
+    record["rec_types"] = info.recTypes;
+    return record;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<shared::rpc::RecordLookUpResponse::RecordError> {
+  static bool
+  decode(Node const &node, shared::rpc::RecordLookUpResponse::RecordError &err)
+  {
+    err.code       = helper::try_extract<std::string>(node, "code");
+    err.recordName = helper::try_extract<std::string>(node, "record_name");
+    err.message    = helper::try_extract<std::string>(node, "message");
+    return true;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+} // namespace YAML
+namespace internal
+{
+template <typename, template <typename> class, typename = std::void_t<>> struct detect_member_function : std::false_type {
+};
+template <typename Codec, template <typename> class Op>
+struct detect_member_function<Codec, Op, std::void_t<Op<Codec>>> : std::true_type {
+};
+// Help to detect if codec implements the right functions.
+template <class Codec> using encode_op = decltype(std::declval<Codec>().encode({}));
+template <class Codec> using decode_op = decltype(std::declval<Codec>().decode({}));
+
+// Compile time check for encode member function
+template <typename Codec> using has_encode = detect_member_function<Codec, encode_op>;
+// Compile time check for decode member function
+template <typename Codec> using has_decode = detect_member_function<Codec, decode_op>;
+} // namespace internal
+/**
+ * Handy classes to deal with the json emitters. If yaml needs to be emitted, then this should be changed and do not use the
+ * double quoted flow.
+ */
+class yamlcpp_json_emitter
+{
+public:
+  static std::string
+  encode(shared::rpc::JSONRPCRequest const &req)
+  {
+    YAML::Emitter json;
+    json << YAML::DoubleQuoted << YAML::Flow;
+    json << YAML::BeginMap;
+
+    if (!req.id.empty()) {
+      json << YAML::Key << "id" << YAML::Value << req.id;
+    }
+    json << YAML::Key << "jsonrpc" << YAML::Value << req.jsonrpc;
+    json << YAML::Key << "method" << YAML::Value << req.get_method();
+    if (!req.params.IsNull()) {
+      json << YAML::Key << "params" << YAML::Value << req.params;
+    }
+    json << YAML::EndMap;
+    return json.c_str();
+  }
+
+  static shared::rpc::JSONRPCResponse
+  decode(const std::string &response)
+  {
+    shared::rpc::JSONRPCResponse resp;
+    resp.fullMsg = YAML::Load(response.data());
+    if (resp.fullMsg.Type() != YAML::NodeType::Map) {
+      throw std::runtime_error{"error parsing response, response is not a structure"};
+    }
+
+    if (resp.fullMsg["result"]) {
+      resp.result = resp.fullMsg["result"];
+    } else if (resp.fullMsg["error"]) {
+      resp.error = resp.fullMsg["error"];
+    }
+
+    if (auto id = resp.fullMsg["id"]) {
+      resp.id = id.as<std::string>();
+    }
+    if (auto jsonrpc = resp.fullMsg["jsonrpc"]) {
+      resp.jsonrpc = jsonrpc.as<std::string>();
+    }
+
+    return resp;
+  }
+};
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index 54d79a3..519bca4 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -265,6 +265,123 @@
 } TSHttpStatus;
 
 /**
+    TSEvents are sent to continuations when they are called back.
+    The TSEvent provides the continuation's handler function with
+    information about the callback. Based on the event it receives,
+    the handler function can decide what to do.
+ */
+typedef enum {
+  TS_EVENT_NONE      = 0,
+  TS_EVENT_IMMEDIATE = 1,
+  TS_EVENT_TIMEOUT   = 2,
+  TS_EVENT_ERROR     = 3,
+  TS_EVENT_CONTINUE  = 4,
+
+  TS_EVENT_VCONN_READ_READY         = 100,
+  TS_EVENT_VCONN_WRITE_READY        = 101,
+  TS_EVENT_VCONN_READ_COMPLETE      = 102,
+  TS_EVENT_VCONN_WRITE_COMPLETE     = 103,
+  TS_EVENT_VCONN_EOS                = 104,
+  TS_EVENT_VCONN_INACTIVITY_TIMEOUT = 105,
+  TS_EVENT_VCONN_ACTIVE_TIMEOUT     = 106,
+  TS_EVENT_VCONN_START              = 107,
+  TS_EVENT_VCONN_CLOSE              = 108,
+  TS_EVENT_VCONN_OUTBOUND_START     = 109,
+  TS_EVENT_VCONN_OUTBOUND_CLOSE     = 110,
+  TS_EVENT_VCONN_PRE_ACCEPT         = TS_EVENT_VCONN_START, // Deprecated but still compatible
+
+  TS_EVENT_NET_CONNECT        = 200,
+  TS_EVENT_NET_CONNECT_FAILED = 201,
+  TS_EVENT_NET_ACCEPT         = 202,
+  TS_EVENT_NET_ACCEPT_FAILED  = 204,
+
+  TS_EVENT_INTERNAL_206 = 206,
+  TS_EVENT_INTERNAL_207 = 207,
+  TS_EVENT_INTERNAL_208 = 208,
+  TS_EVENT_INTERNAL_209 = 209,
+  TS_EVENT_INTERNAL_210 = 210,
+  TS_EVENT_INTERNAL_211 = 211,
+  TS_EVENT_INTERNAL_212 = 212,
+
+  TS_EVENT_HOST_LOOKUP = 500,
+
+  TS_EVENT_CACHE_OPEN_READ              = 1102,
+  TS_EVENT_CACHE_OPEN_READ_FAILED       = 1103,
+  TS_EVENT_CACHE_OPEN_WRITE             = 1108,
+  TS_EVENT_CACHE_OPEN_WRITE_FAILED      = 1109,
+  TS_EVENT_CACHE_REMOVE                 = 1112,
+  TS_EVENT_CACHE_REMOVE_FAILED          = 1113,
+  TS_EVENT_CACHE_SCAN                   = 1120,
+  TS_EVENT_CACHE_SCAN_FAILED            = 1121,
+  TS_EVENT_CACHE_SCAN_OBJECT            = 1122,
+  TS_EVENT_CACHE_SCAN_OPERATION_BLOCKED = 1123,
+  TS_EVENT_CACHE_SCAN_OPERATION_FAILED  = 1124,
+  TS_EVENT_CACHE_SCAN_DONE              = 1125,
+  TS_EVENT_CACHE_LOOKUP                 = 1126,
+  TS_EVENT_CACHE_READ                   = 1127,
+  TS_EVENT_CACHE_DELETE                 = 1128,
+  TS_EVENT_CACHE_WRITE                  = 1129,
+  TS_EVENT_CACHE_WRITE_HEADER           = 1130,
+  TS_EVENT_CACHE_CLOSE                  = 1131,
+  TS_EVENT_CACHE_LOOKUP_READY           = 1132,
+  TS_EVENT_CACHE_LOOKUP_COMPLETE        = 1133,
+  TS_EVENT_CACHE_READ_READY             = 1134,
+  TS_EVENT_CACHE_READ_COMPLETE          = 1135,
+
+  TS_EVENT_INTERNAL_1200 = 1200,
+
+  TS_EVENT_SSL_SESSION_GET    = 2000,
+  TS_EVENT_SSL_SESSION_NEW    = 2001,
+  TS_EVENT_SSL_SESSION_REMOVE = 2002,
+
+  TS_EVENT_AIO_DONE = 3900,
+
+  TS_EVENT_HTTP_CONTINUE                     = 60000,
+  TS_EVENT_HTTP_ERROR                        = 60001,
+  TS_EVENT_HTTP_READ_REQUEST_HDR             = 60002,
+  TS_EVENT_HTTP_OS_DNS                       = 60003,
+  TS_EVENT_HTTP_SEND_REQUEST_HDR             = 60004,
+  TS_EVENT_HTTP_READ_CACHE_HDR               = 60005,
+  TS_EVENT_HTTP_READ_RESPONSE_HDR            = 60006,
+  TS_EVENT_HTTP_SEND_RESPONSE_HDR            = 60007,
+  TS_EVENT_HTTP_REQUEST_TRANSFORM            = 60008,
+  TS_EVENT_HTTP_RESPONSE_TRANSFORM           = 60009,
+  TS_EVENT_HTTP_SELECT_ALT                   = 60010,
+  TS_EVENT_HTTP_TXN_START                    = 60011,
+  TS_EVENT_HTTP_TXN_CLOSE                    = 60012,
+  TS_EVENT_HTTP_SSN_START                    = 60013,
+  TS_EVENT_HTTP_SSN_CLOSE                    = 60014,
+  TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE        = 60015,
+  TS_EVENT_HTTP_PRE_REMAP                    = 60016,
+  TS_EVENT_HTTP_POST_REMAP                   = 60017,
+  TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE = 60018,
+  TS_EVENT_HTTP_RESPONSE_CLIENT              = 60019,
+  TS_EVENT_HTTP_REQUEST_CLIENT               = 60020,
+
+  TS_EVENT_LIFECYCLE_PORTS_INITIALIZED          = 60100,
+  TS_EVENT_LIFECYCLE_PORTS_READY                = 60101,
+  TS_EVENT_LIFECYCLE_CACHE_READY                = 60102,
+  TS_EVENT_LIFECYCLE_SERVER_SSL_CTX_INITIALIZED = 60103,
+  TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED = 60104,
+  TS_EVENT_LIFECYCLE_MSG                        = 60105,
+  TS_EVENT_LIFECYCLE_TASK_THREADS_READY         = 60106,
+  TS_EVENT_LIFECYCLE_SHUTDOWN                   = 60107,
+
+  TS_EVENT_INTERNAL_60200    = 60200,
+  TS_EVENT_INTERNAL_60201    = 60201,
+  TS_EVENT_INTERNAL_60202    = 60202,
+  TS_EVENT_SSL_CERT          = 60203,
+  TS_EVENT_SSL_SERVERNAME    = 60204,
+  TS_EVENT_SSL_VERIFY_SERVER = 60205,
+  TS_EVENT_SSL_VERIFY_CLIENT = 60206,
+  TS_EVENT_SSL_CLIENT_HELLO  = 60207,
+  TS_EVENT_SSL_SECRET        = 60208,
+
+  TS_EVENT_MGMT_UPDATE = 60300
+} TSEvent;
+#define TS_EVENT_HTTP_READ_REQUEST_PRE_REMAP TS_EVENT_HTTP_PRE_REMAP /* backwards compat */
+
+/**
     This set of enums represents the possible hooks where you can
     set up continuation callbacks. The functions used to register a
     continuation for a particular hook are:
@@ -274,6 +391,7 @@
      - TS_HTTP_REQUEST_TRANSFORM_HOOK
      - TS_HTTP_RESPONSE_TRANSFORM_HOOK
      - TS_HTTP_RESPONSE_CLIENT_HOOK
+     - TS_HTTP_REQUEST_CLIENT_HOOK
 
     The following hooks can ONLY be added globally:
      - TS_HTTP_SELECT_ALT_HOOK
@@ -290,6 +408,7 @@
      - TS_HTTP_REQUEST_TRANSFORM_HOOK
      - TS_HTTP_RESPONSE_TRANSFORM_HOOK
      - TS_HTTP_RESPONSE_CLIENT_HOOK
+     - TS_HTTP_REQUEST_CLIENT_HOOK
      - TS_HTTP_TXN_START_HOOK
      - TS_HTTP_TXN_CLOSE_HOOK
      - TS_HTTP_SSN_CLOSE_HOOK
@@ -316,28 +435,53 @@
     TS_HTTP_LAST_HOOK _must_ be the last element. Only right place
     to insert a new element is just before TS_HTTP_LAST_HOOK.
 
+
+    @note The TS_HTTP hooks below have to be in the same order as their
+    corresponding TS_EVENT counterparts.  We use this in calculating the
+    corresponding event from a hook ID by summing
+    TS_EVENT_HTTP_READ_REQUEST_HDR with the hook ID (see the invoke call in
+    HttpSM::state_api_callout). For example, the following expression must be
+    true:
+
+    TS_EVENT_HTTP_TXN_CLOSE == TS_EVENT_HTTP_READ_REQUEST_HDR + TS_HTTP_TXN_CLOSE_HOOK
+
  */
 typedef enum {
-  TS_HTTP_READ_REQUEST_HDR_HOOK,
-  TS_HTTP_OS_DNS_HOOK,
-  TS_HTTP_SEND_REQUEST_HDR_HOOK,
-  TS_HTTP_READ_CACHE_HDR_HOOK,
-  TS_HTTP_READ_RESPONSE_HDR_HOOK,
-  TS_HTTP_SEND_RESPONSE_HDR_HOOK,
-  TS_HTTP_REQUEST_TRANSFORM_HOOK,
-  TS_HTTP_RESPONSE_TRANSFORM_HOOK,
-  TS_HTTP_SELECT_ALT_HOOK,
-  TS_HTTP_TXN_START_HOOK,
-  TS_HTTP_TXN_CLOSE_HOOK,
-  TS_HTTP_SSN_START_HOOK,
-  TS_HTTP_SSN_CLOSE_HOOK,
-  TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK,
-  TS_HTTP_PRE_REMAP_HOOK,
-  TS_HTTP_POST_REMAP_HOOK,
-  TS_HTTP_RESPONSE_CLIENT_HOOK,
+/*
+   The following macro helps maintain the relationship between the TS_HTTP
+   hooks and their TS_EVENT_HTTP counterparts as described in the above
+   doxygen comment.
+ */
+#define DERIVE_HOOK_VALUE_FROM_EVENT(COMMON) TS_HTTP_##COMMON##_HOOK = TS_EVENT_HTTP_##COMMON - TS_EVENT_HTTP_READ_REQUEST_HDR
+  DERIVE_HOOK_VALUE_FROM_EVENT(READ_REQUEST_HDR),             // TS_HTTP_READ_REQUEST_HDR_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(OS_DNS),                       // TS_HTTP_OS_DNS_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(SEND_REQUEST_HDR),             // TS_HTTP_SEND_REQUEST_HDR_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(READ_CACHE_HDR),               // TS_HTTP_READ_CACHE_HDR_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(READ_RESPONSE_HDR),            // TS_HTTP_READ_RESPONSE_HDR_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(SEND_RESPONSE_HDR),            // TS_HTTP_SEND_RESPONSE_HDR_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(REQUEST_TRANSFORM),            // TS_HTTP_REQUEST_TRANSFORM_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(RESPONSE_TRANSFORM),           // TS_HTTP_RESPONSE_TRANSFORM_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(SELECT_ALT),                   // TS_HTTP_SELECT_ALT_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(TXN_START),                    // TS_HTTP_TXN_START_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(TXN_CLOSE),                    // TS_HTTP_TXN_CLOSE_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(SSN_START),                    // TS_HTTP_SSN_START_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(SSN_CLOSE),                    // TS_HTTP_SSN_CLOSE_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(CACHE_LOOKUP_COMPLETE),        // TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(PRE_REMAP),                    // TS_HTTP_PRE_REMAP_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(POST_REMAP),                   // TS_HTTP_POST_REMAP_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(REQUEST_BUFFER_READ_COMPLETE), // TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(RESPONSE_CLIENT),              // TS_HTTP_RESPONSE_CLIENT_HOOK
+  DERIVE_HOOK_VALUE_FROM_EVENT(REQUEST_CLIENT),               // TS_HTTP_REQUEST_CLIENT_HOOK
+
+// NOTE:
+// If adding any TS_HTTP hooks, be sure to understand the note above in the
+// doxygen comment about the ordering of these enum values with respect to
+// their corresponding TS_EVENT values.
+#undef DERIVE_HOOK_VALUE_FROM_EVENT
+
   // Putting the SSL hooks in the same enum space
   // So both sets of hooks can be set by the same Hook function
-  TS_SSL_FIRST_HOOK,
+  TS_SSL_FIRST_HOOK        = 201,
   TS_VCONN_START_HOOK      = TS_SSL_FIRST_HOOK,
   TS_VCONN_PRE_ACCEPT_HOOK = TS_VCONN_START_HOOK, // Deprecated but compatible for now.
   TS_VCONN_CLOSE_HOOK,
@@ -352,7 +496,6 @@
   TS_VCONN_OUTBOUND_START_HOOK,
   TS_VCONN_OUTBOUND_CLOSE_HOOK,
   TS_SSL_LAST_HOOK = TS_VCONN_OUTBOUND_CLOSE_HOOK,
-  TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK,
   TS_HTTP_LAST_HOOK
 } TSHttpHookID;
 
@@ -442,122 +585,6 @@
   TS_LIFECYCLE_LAST_HOOK
 } TSLifecycleHookID;
 
-/**
-    TSEvents are sent to continuations when they are called back.
-    The TSEvent provides the continuation's handler function with
-    information about the callback. Based on the event it receives,
-    the handler function can decide what to do.
-
- */
-typedef enum {
-  TS_EVENT_NONE      = 0,
-  TS_EVENT_IMMEDIATE = 1,
-  TS_EVENT_TIMEOUT   = 2,
-  TS_EVENT_ERROR     = 3,
-  TS_EVENT_CONTINUE  = 4,
-
-  TS_EVENT_VCONN_READ_READY         = 100,
-  TS_EVENT_VCONN_WRITE_READY        = 101,
-  TS_EVENT_VCONN_READ_COMPLETE      = 102,
-  TS_EVENT_VCONN_WRITE_COMPLETE     = 103,
-  TS_EVENT_VCONN_EOS                = 104,
-  TS_EVENT_VCONN_INACTIVITY_TIMEOUT = 105,
-  TS_EVENT_VCONN_ACTIVE_TIMEOUT     = 106,
-  TS_EVENT_VCONN_START              = 107,
-  TS_EVENT_VCONN_CLOSE              = 108,
-  TS_EVENT_VCONN_OUTBOUND_START     = 109,
-  TS_EVENT_VCONN_OUTBOUND_CLOSE     = 110,
-  TS_EVENT_VCONN_PRE_ACCEPT         = TS_EVENT_VCONN_START, // Deprecated but still compatible
-
-  TS_EVENT_NET_CONNECT        = 200,
-  TS_EVENT_NET_CONNECT_FAILED = 201,
-  TS_EVENT_NET_ACCEPT         = 202,
-  TS_EVENT_NET_ACCEPT_FAILED  = 204,
-
-  TS_EVENT_INTERNAL_206 = 206,
-  TS_EVENT_INTERNAL_207 = 207,
-  TS_EVENT_INTERNAL_208 = 208,
-  TS_EVENT_INTERNAL_209 = 209,
-  TS_EVENT_INTERNAL_210 = 210,
-  TS_EVENT_INTERNAL_211 = 211,
-  TS_EVENT_INTERNAL_212 = 212,
-
-  TS_EVENT_HOST_LOOKUP = 500,
-
-  TS_EVENT_CACHE_OPEN_READ              = 1102,
-  TS_EVENT_CACHE_OPEN_READ_FAILED       = 1103,
-  TS_EVENT_CACHE_OPEN_WRITE             = 1108,
-  TS_EVENT_CACHE_OPEN_WRITE_FAILED      = 1109,
-  TS_EVENT_CACHE_REMOVE                 = 1112,
-  TS_EVENT_CACHE_REMOVE_FAILED          = 1113,
-  TS_EVENT_CACHE_SCAN                   = 1120,
-  TS_EVENT_CACHE_SCAN_FAILED            = 1121,
-  TS_EVENT_CACHE_SCAN_OBJECT            = 1122,
-  TS_EVENT_CACHE_SCAN_OPERATION_BLOCKED = 1123,
-  TS_EVENT_CACHE_SCAN_OPERATION_FAILED  = 1124,
-  TS_EVENT_CACHE_SCAN_DONE              = 1125,
-  TS_EVENT_CACHE_LOOKUP                 = 1126,
-  TS_EVENT_CACHE_READ                   = 1127,
-  TS_EVENT_CACHE_DELETE                 = 1128,
-  TS_EVENT_CACHE_WRITE                  = 1129,
-  TS_EVENT_CACHE_WRITE_HEADER           = 1130,
-  TS_EVENT_CACHE_CLOSE                  = 1131,
-  TS_EVENT_CACHE_LOOKUP_READY           = 1132,
-  TS_EVENT_CACHE_LOOKUP_COMPLETE        = 1133,
-  TS_EVENT_CACHE_READ_READY             = 1134,
-  TS_EVENT_CACHE_READ_COMPLETE          = 1135,
-
-  TS_EVENT_INTERNAL_1200 = 1200,
-
-  TS_EVENT_SSL_SESSION_GET    = 2000,
-  TS_EVENT_SSL_SESSION_NEW    = 2001,
-  TS_EVENT_SSL_SESSION_REMOVE = 2002,
-
-  TS_EVENT_AIO_DONE = 3900,
-
-  TS_EVENT_HTTP_CONTINUE                = 60000,
-  TS_EVENT_HTTP_ERROR                   = 60001,
-  TS_EVENT_HTTP_READ_REQUEST_HDR        = 60002,
-  TS_EVENT_HTTP_OS_DNS                  = 60003,
-  TS_EVENT_HTTP_SEND_REQUEST_HDR        = 60004,
-  TS_EVENT_HTTP_READ_CACHE_HDR          = 60005,
-  TS_EVENT_HTTP_READ_RESPONSE_HDR       = 60006,
-  TS_EVENT_HTTP_SEND_RESPONSE_HDR       = 60007,
-  TS_EVENT_HTTP_REQUEST_TRANSFORM       = 60008,
-  TS_EVENT_HTTP_RESPONSE_TRANSFORM      = 60009,
-  TS_EVENT_HTTP_SELECT_ALT              = 60010,
-  TS_EVENT_HTTP_TXN_START               = 60011,
-  TS_EVENT_HTTP_TXN_CLOSE               = 60012,
-  TS_EVENT_HTTP_SSN_START               = 60013,
-  TS_EVENT_HTTP_SSN_CLOSE               = 60014,
-  TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE   = 60015,
-  TS_EVENT_HTTP_PRE_REMAP               = 60016,
-  TS_EVENT_HTTP_POST_REMAP              = 60017,
-  TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE = 60018,
-
-  TS_EVENT_LIFECYCLE_PORTS_INITIALIZED          = 60100,
-  TS_EVENT_LIFECYCLE_PORTS_READY                = 60101,
-  TS_EVENT_LIFECYCLE_CACHE_READY                = 60102,
-  TS_EVENT_LIFECYCLE_SERVER_SSL_CTX_INITIALIZED = 60103,
-  TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED = 60104,
-  TS_EVENT_LIFECYCLE_MSG                        = 60105,
-  TS_EVENT_LIFECYCLE_TASK_THREADS_READY         = 60106,
-  TS_EVENT_LIFECYCLE_SHUTDOWN                   = 60107,
-
-  TS_EVENT_INTERNAL_60200    = 60200,
-  TS_EVENT_INTERNAL_60201    = 60201,
-  TS_EVENT_INTERNAL_60202    = 60202,
-  TS_EVENT_SSL_CERT          = 60203,
-  TS_EVENT_SSL_SERVERNAME    = 60204,
-  TS_EVENT_SSL_VERIFY_SERVER = 60205,
-  TS_EVENT_SSL_VERIFY_CLIENT = 60206,
-  TS_EVENT_SSL_CLIENT_HELLO  = 60207,
-  TS_EVENT_SSL_SECRET        = 60208,
-
-  TS_EVENT_MGMT_UPDATE = 60300
-} TSEvent;
-#define TS_EVENT_HTTP_READ_REQUEST_PRE_REMAP TS_EVENT_HTTP_PRE_REMAP /* backwards compat */
-
 typedef enum {
   TS_SRVSTATE_STATE_UNDEFINED = 0,
   TS_SRVSTATE_ACTIVE_TIMEOUT,
@@ -788,10 +815,7 @@
   TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DEAD_SERVER,
   TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES,
   TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT,
-  TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT,
   TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME,
-  // Should be removed for 10.0
-  TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD,
   TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS,
   TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT,
   TS_CONFIG_HTTP_RESPONSE_SERVER_STR,
@@ -846,7 +870,6 @@
   TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD,
   TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME,
   TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS,
-  TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT,
   TS_CONFIG_HTTP_NORMALIZE_AE,
   TS_CONFIG_HTTP_INSERT_FORWARDED,
   TS_CONFIG_HTTP_PROXY_PROTOCOL_OUT,
@@ -856,16 +879,12 @@
   TS_CONFIG_HTTP_SERVER_MIN_KEEP_ALIVE_CONNS,
   TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX,
   TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH,
-#if TS_VERSION_MAJOR < 10
-  /* This is kept in the 9.x releases to preserve the ABI. Remove this in the
-   * 10 release. */
-  TS_CONFIG_SSL_CLIENT_VERIFY_SERVER,
-#endif
   TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY,
   TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES,
   TS_CONFIG_SSL_CLIENT_SNI_POLICY,
   TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME,
   TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME,
+  TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS,
   TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE,
   TS_CONFIG_HTTP_CONNECT_DEAD_POLICY,
   TS_CONFIG_HTTP_MAX_PROXY_CYCLES,
@@ -1020,6 +1039,7 @@
 typedef struct tsapi_hostlookupresult *TSHostLookupResult;
 typedef struct tsapi_aiocallback *TSAIOCallback;
 typedef struct tsapi_net_accept *TSAcceptor;
+typedef struct tsapi_remap_plugin_info *TSRemapPluginInfo;
 
 typedef struct tsapi_fetchsm *TSFetchSM;
 
@@ -1370,9 +1390,9 @@
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_1;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_2_0;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3;
-extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3_D27;
+extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3_D29;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC;
-extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D27;
+extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D29;
 
 extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9;
 extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0;
@@ -1461,6 +1481,22 @@
 }
 #endif
 
+// JSONRPC 2.0 related interface.
+typedef struct tsapi_rpcproviderhandle *TSRPCProviderHandle;
+typedef struct tsapi_yaml *TSYaml;
+
+///
+/// @brief JSON-RPC Handler options
+///
+/// This class holds information about how a handler will be managed and delivered when called. The JSON-RPC manager would use this
+/// object to perform certain validation.
+///
+typedef struct TSRPCHandlerOptions_s {
+  struct Auth {
+    int restricted; ///< Tells the RPC Manager if the call can be delivered or not based on the config rules.
+  } auth;
+} TSRPCHandlerOptions;
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/include/ts/experimental.h b/include/ts/experimental.h
index b2d020f..3099090 100644
--- a/include/ts/experimental.h
+++ b/include/ts/experimental.h
@@ -73,29 +73,6 @@
 tsapi int TSMimeHdrFieldEqual(TSMBuffer bufp, TSMLoc hdr_obj, TSMLoc field1, TSMLoc field2);
 tsapi TSReturnCode TSHttpTxnHookRegisteredFor(TSHttpTxn txnp, TSHttpHookID id, TSEventFunc funcp);
 
-#if TS_VERSION_MAJOR < 10
-
-/* These are kept in the 9.x releases to preserve the ABI. These should be
- * removed in the ATS 10 release. */
-
-/* Various HTTP "control" modes */
-typedef enum {
-  TS_HTTP_CNTL_GET_LOGGING_MODE,
-  TS_HTTP_CNTL_SET_LOGGING_MODE,
-  TS_HTTP_CNTL_GET_INTERCEPT_RETRY_MODE,
-  TS_HTTP_CNTL_SET_INTERCEPT_RETRY_MODE
-} TSHttpCntlTypeExperimental;
-
-#define TS_HTTP_CNTL_OFF (void *)0
-#define TS_HTTP_CNTL_ON (void *)1
-/* usage:
-   void *onoff = 0;
-   TSHttpTxnCntl(.., TS_HTTP_CNTL_GET_LOGGING_MODE, &onoff);
-   if (onoff == TS_HTTP_CNTL_ON) ....
-*/
-tsapi TSReturnCode TSHttpTxnCntl(TSHttpTxn txnp, TSHttpCntlTypeExperimental cntl, void *data);
-
-#endif
 /* Protocols APIs */
 tsapi void TSVConnCacheHttpInfoSet(TSVConn connp, TSCacheHttpInfo infop);
 
@@ -350,6 +327,7 @@
 /****************************************************************************
  *  Set a records.config integer variable
  ****************************************************************************/
+// DEPRECATED
 tsapi TSReturnCode TSMgmtConfigIntSet(const char *var_name, TSMgmtInt value);
 
 tsapi TSReturnCode TSMgmtConfigFileAdd(const char *parent, const char *fileName);
diff --git a/include/ts/remap.h b/include/ts/remap.h
index 1e804b9..49a38e0 100644
--- a/include/ts/remap.h
+++ b/include/ts/remap.h
@@ -38,6 +38,7 @@
 typedef struct _tsremap_api_info {
   unsigned long size;            /* in: sizeof(struct _tsremap_api_info) */
   unsigned long tsremap_version; /* in: TS supported version ((major << 16) | minor) */
+  TSRemapPluginInfo plugin_info; /* in: Pointer to the internal RemapPluginInst */
 } TSRemapInterface;
 
 typedef struct _tm_remap_request_info {
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 80c61b3..3cda607 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1246,10 +1246,8 @@
 tsapi void TSContDestroy(TSCont contp);
 tsapi void TSContDataSet(TSCont contp, void *data);
 tsapi void *TSContDataGet(TSCont contp);
-tsapi TSAction TSContSchedule(TSCont contp, TSHRTime timeout);
 tsapi TSAction TSContScheduleOnPool(TSCont contp, TSHRTime timeout, TSThreadPool tp);
 tsapi TSAction TSContScheduleOnThread(TSCont contp, TSHRTime timeout, TSEventThread ethread);
-tsapi TSAction TSContScheduleEvery(TSCont contp, TSHRTime every /* millisecs */);
 tsapi TSAction TSContScheduleEveryOnPool(TSCont contp, TSHRTime every /* millisecs */, TSThreadPool tp);
 tsapi TSAction TSContScheduleEveryOnThread(TSCont contp, TSHRTime every /* millisecs */, TSEventThread ethread);
 tsapi TSReturnCode TSContThreadAffinitySet(TSCont contp, TSEventThread ethread);
@@ -1640,24 +1638,6 @@
 tsapi void TSUserArgSet(void *data, int arg_idx, void *arg);
 tsapi void *TSUserArgGet(void *data, int arg_idx);
 
-/* These are deprecated as of v9.0.0, and will be removed in v10.0.0 */
-tsapi TS_DEPRECATED void TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void *arg);
-tsapi TS_DEPRECATED void *TSHttpTxnArgGet(TSHttpTxn txnp, int arg_idx);
-tsapi TS_DEPRECATED void TSHttpSsnArgSet(TSHttpSsn ssnp, int arg_idx, void *arg);
-tsapi TS_DEPRECATED void *TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx);
-tsapi TS_DEPRECATED void TSVConnArgSet(TSVConn connp, int arg_idx, void *arg);
-tsapi TS_DEPRECATED void *TSVConnArgGet(TSVConn connp, int arg_idx);
-
-tsapi TS_DEPRECATED TSReturnCode TSHttpTxnArgIndexReserve(const char *name, const char *description, int *arg_idx);
-tsapi TS_DEPRECATED TSReturnCode TSHttpTxnArgIndexNameLookup(const char *name, int *arg_idx, const char **description);
-tsapi TS_DEPRECATED TSReturnCode TSHttpTxnArgIndexLookup(int arg_idx, const char **name, const char **description);
-tsapi TS_DEPRECATED TSReturnCode TSHttpSsnArgIndexReserve(const char *name, const char *description, int *arg_idx);
-tsapi TS_DEPRECATED TSReturnCode TSHttpSsnArgIndexNameLookup(const char *name, int *arg_idx, const char **description);
-tsapi TS_DEPRECATED TSReturnCode TSHttpSsnArgIndexLookup(int arg_idx, const char **name, const char **description);
-tsapi TS_DEPRECATED TSReturnCode TSVConnArgIndexReserve(const char *name, const char *description, int *arg_idx);
-tsapi TS_DEPRECATED TSReturnCode TSVConnArgIndexNameLookup(const char *name, int *arg_idx, const char **description);
-tsapi TS_DEPRECATED TSReturnCode TSVConnArgIndexLookup(int arg_idx, const char **name, const char **description);
-
 tsapi void TSHttpTxnStatusSet(TSHttpTxn txnp, TSHttpStatus status);
 tsapi TSHttpStatus TSHttpTxnStatusGet(TSHttpTxn txnp);
 
@@ -2431,10 +2411,11 @@
 
 /**
     Check if transaction was aborted (due client/server errors etc.)
+    Client_abort is set as True, in case the abort was caused by the Client.
 
     @return 1 if transaction was aborted
 */
-tsapi TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp);
+tsapi TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp, bool *client_abort);
 
 tsapi TSVConn TSVConnCreate(TSEventFunc event_funcp, TSMutex mutexp);
 tsapi TSVConn TSVConnFdCreate(int fd);
@@ -2690,6 +2671,9 @@
 //
 tsapi TSReturnCode TSRemapToUrlGet(TSHttpTxn txnp, TSMLoc *urlLocp);
 
+// Get some plugin details from the TSRemapPluginInfo
+tsapi void *TSRemapDLHandleGet(TSRemapPluginInfo plugin_info);
+
 // Override response behavior, and hard-set the state machine for whether to succeed or fail, and how.
 tsapi void TSHttpTxnResponseActionSet(TSHttpTxn txnp, TSResponseAction *action);
 
@@ -2763,6 +2747,94 @@
 tsapi bool TSHttpTxnCntlGet(TSHttpTxn txnp, TSHttpCntlType ctrl);
 tsapi TSReturnCode TSHttpTxnCntlSet(TSHttpTxn txnp, TSHttpCntlType ctrl, bool data);
 
+/**
+ * JSONRPC callback signature for method calls.
+ */
+typedef void (*TSRPCMethodCb)(const char *id, TSYaml params);
+/**
+ * JSONRPC callback signature for notification calls
+ */
+typedef void (*TSRPCNotificationCb)(TSYaml params);
+
+/**
+ * @brief Method to perform a registration and validation when a plugin is expected to handle JSONRPC calls.
+ *
+ * @note YAMLCPP The JSONRPC library will only provide binary compatibility within the life-span of a major release. Plugins must
+ * check-in if they intent to handle RPC commands, passing their yamlcpp library version this function will validate it against the
+ * one used internally in TS.
+ *
+ * @param provider_name The name of the provider.
+ * @param provider_len The length of the provider string.
+ * @param yamlcpp_lib_version a string with the yamlcpp library version.
+ * @param yamlcpp_lib_len The length of the yamlcpp_lib_len string.
+ * @return A new TSRPCProviderHandle, nullptr if the yamlcpp_lib_version was not set, or the yamlcpp version does not match with
+ * the one used internally in TS. The returned TSRPCProviderHandle will be set with the provider's name. The caller should pass the
+ * returned TSRPCProviderHandle object to each subsequent TSRPCRegisterMethod/Notification* call.
+ */
+tsapi TSRPCProviderHandle TSRPCRegister(const char *provider_name, size_t provider_len, const char *yamlcpp_lib_version,
+                                        size_t yamlcpp_lib_len);
+
+/**
+ * @brief Add new registered method handler to the JSON RPC engine.
+ *
+ * @param name Call name to be exposed by the RPC Engine, this should match the incoming request. i.e: If you register 'get_stats'
+ *             then the incoming jsonrpc call should have this very same name in the 'method' field. .. {...'method':
+ *             'get_stats'...} .
+ * @param name_len The length of the name string.
+ * @param callback  The function to be registered. See @c TSRPCMethodCb
+ * @param info TSRPCProviderHandle pointer, this will be used to provide more context information about this call. This object
+ * ideally should be the one returned by the TSRPCRegister API.
+ * @param opt Pointer to @c TSRPCHandlerOptions object. This will be used to store specifics about a particular call, the rpc
+ *            manager will use this object to perform certain actions. A copy of this object wil be stored by the rpc manager.
+ *
+ * @return TS_SUCCESS if the handler was successfully registered, TS_ERROR if the handler is already registered.
+ */
+tsapi TSReturnCode TSRPCRegisterMethodHandler(const char *name, size_t name_len, TSRPCMethodCb callback, TSRPCProviderHandle info,
+                                              const TSRPCHandlerOptions *opt);
+
+/**
+ * @brief Add new registered notification handler to the JSON RPC engine.
+ *
+ * @param name Call name to be exposed by the RPC Engine, this should match the incoming request. i.e: If you register 'get_stats'
+ *             then the incoming jsonrpc call should have this very same name in the 'method' field. .. {...'method':
+ *             'get_stats'...} .
+ * @param name_len The length of the name string.
+ * @param callback  The function to be registered. See @c TSRPCNotificationCb
+ * @param info TSRPCProviderHandle pointer, this will be used to provide more description for instance, when logging before or after
+ * a call. This object ideally should be the one returned by the TSRPCRegister API.
+ * @param opt Pointer to @c TSRPCHandlerOptions object. This will be used to store specifics about a particular call, the rpc
+ *            manager will use this object to perform certain actions. A copy of this object wil be stored by the rpc manager.
+ * @return TS_SUCCESS if the handler was successfully registered, TS_ERROR if the handler is already registered.
+ */
+tsapi TSReturnCode TSRPCRegisterNotificationHandler(const char *name, size_t name_len, TSRPCNotificationCb callback,
+                                                    TSRPCProviderHandle info, const TSRPCHandlerOptions *opt);
+
+/**
+ * @brief Function to notify the JSONRPC engine that the current handler is done working.
+ *
+ * This function must be used when implementing a 'method' rpc handler. Once the work is done and the response is ready to be sent
+ * back to the client, this function should be called. Is expected to set the YAML node as response. If the response is empty a
+ * 'success' message will be added to the client's response.
+ *
+ * @note This should not be used if you registered your handler as a notification: @c TSRPCNotificationCb
+ * @param resp The YAML node that contains the call response.
+ * @return TS_SUCCESS if no issues. TS_ERROR otherwise.
+ */
+tsapi TSReturnCode TSRPCHandlerDone(TSYaml resp);
+
+/**
+ * @brief Function to notify the JSONRPC engine that the current handler is done working and an error has arisen.
+ *
+ * @note This should not be used if you registered your handler as a notification: @c TSRPCNotificationCb
+ * call.
+ * @param code Error code.
+ * @param descr A text with a description of the error.
+ * @param descr_len The length of the descrition string.
+ * @note The @c code and @c descr will be part of the @c 'data' field in the jsonrpc error response.
+ * @return TS_SUCCESS if no issues. TS_ERROR otherwise.
+ */
+tsapi TSReturnCode TSRPCHandlerError(int code, const char *descr, size_t descr_len);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/include/tscore/ArgParser.h b/include/tscore/ArgParser.h
index a7b4b7a..2e6b93e 100644
--- a/include/tscore/ArgParser.h
+++ b/include/tscore/ArgParser.h
@@ -250,6 +250,9 @@
   // get the error message
   std::string get_error() const;
 
+  // Add App's description.
+  void add_description(std::string const &descr);
+
 protected:
   // Converted from 'const char **argv' for the use of parsing and help
   AP_StrVec _argv;
diff --git a/include/tscore/BufferWriter.h b/include/tscore/BufferWriter.h
index 85d0a74..0d1758a 100644
--- a/include/tscore/BufferWriter.h
+++ b/include/tscore/BufferWriter.h
@@ -31,8 +31,9 @@
 #include <iosfwd>
 #include <string_view>
 
+#include "swoc/MemSpan.h"
+
 #include "tscpp/util/TextView.h"
-#include "tscpp/util/MemSpan.h"
 #include "tscore/BufferWriterForward.h"
 
 namespace ts
@@ -237,7 +238,7 @@
   FixedBufferWriter(FixedBufferWriter &&)                 = delete;
   FixedBufferWriter &operator=(FixedBufferWriter &&) = delete;
 
-  FixedBufferWriter(MemSpan<char> &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}
+  FixedBufferWriter(swoc::MemSpan<char> &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}
 
   /// Write a single character @a c to the buffer.
   FixedBufferWriter &
@@ -732,7 +733,7 @@
 }
 
 // MemSpan
-BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan<void> const &span);
+BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, swoc::MemSpan<void> const &span);
 
 // -- Common formatters --
 
diff --git a/include/tscore/DiagsTypes.h b/include/tscore/DiagsTypes.h
index e1e8fd3..b9d1145 100644
--- a/include/tscore/DiagsTypes.h
+++ b/include/tscore/DiagsTypes.h
@@ -43,6 +43,8 @@
 #include "SourceLocation.h"
 #include "DbgCtl.h"
 
+#include "tscpp/util/ts_diag_levels.h"
+
 #define DIAGS_MAGIC 0x12345678
 #define BYTES_IN_MB 1000000
 
@@ -59,19 +61,6 @@
   bool to_diagslog;
 };
 
-enum DiagsLevel { // do not renumber --- used as array index
-  DL_Diag = 0,    // process does not die
-  DL_Debug,       // process does not die
-  DL_Status,      // process does not die
-  DL_Note,        // process does not die
-  DL_Warning,     // process does not die
-  DL_Error,       // process does not die
-  DL_Fatal,       // causes process termination
-  DL_Alert,       // causes process termination
-  DL_Emergency,   // causes process termination, exits with UNRECOVERABLE_EXIT
-  DL_Undefined    // must be last, used for size!
-};
-
 enum StdStream { STDOUT = 0, STDERR };
 
 enum RollingEnabledValues { NO_ROLLING = 0, ROLL_ON_TIME, ROLL_ON_SIZE, ROLL_ON_TIME_OR_SIZE, INVALID_ROLLING_VALUE };
@@ -114,7 +103,7 @@
 //      * debugging tags to selectively enable & disable diagnostics
 //      * action tags to selectively enable & disable code paths
 //      * configurable output to stdout, stderr, syslog, error logs
-//      * traffic_manager interface supporting on-the-fly reconfiguration
+//      * interface to supporting on-the-fly reconfiguration
 //
 //////////////////////////////////////////////////////////////////////////////
 
diff --git a/include/tscore/Errata.h b/include/tscore/Errata.h
index b06d664..227a945 100644
--- a/include/tscore/Errata.h
+++ b/include/tscore/Errata.h
@@ -63,12 +63,12 @@
  */
 
 #pragma once
-
 #include <memory>
 #include <string>
 #include <iosfwd>
 #include <sstream>
 #include <deque>
+#include <system_error>
 #include "NumericType.h"
 #include "IntrusivePtr.h"
 
@@ -138,6 +138,9 @@
   Errata(Message const &msg ///< Message to push
   );
 
+  /// Constructor with @a id and @a std::error_code
+  Errata(std::error_code const &ec ///< Standard error code.
+  );
   /// Move constructor.
   Errata(self &&that);
   /// Move constructor from @c Message.
@@ -570,6 +573,11 @@
   RvBase(Errata const &s ///< Status to copy
   );
 
+  /** Construct with specific status.
+   */
+  RvBase(Errata &&s ///< Status to move
+  );
+
   //! Test the return value for success.
   bool isOK() const;
 
@@ -624,6 +632,7 @@
      Errata const &s  ///< A pre-existing status object
   );
 
+  Rv(Errata &&errata);
   /** User conversion to the result type.
 
       This makes it easy to use the function normally or to pass the
@@ -815,6 +824,12 @@
 {
   this->push(std::move(msg));
 }
+inline Errata::Errata(std::error_code const &ec)
+{
+  auto cond = ec.category().default_error_condition(ec.value());
+  this->push(cond.value(), // we use the classification from the error_condition.
+             ec.value(), ec.message());
+}
 
 inline Errata::operator bool() const
 {
@@ -862,6 +877,16 @@
   return *this;
 }
 
+inline Errata &
+Errata::push(Errata const &err)
+{
+  for (auto const &e : err) {
+    this->push(e.m_id, e.m_code, e.m_text);
+    // e.m_errata??
+  }
+  return *this;
+}
+
 inline Errata::Message const &
 Errata::top() const
 {
@@ -939,6 +964,7 @@
 
 inline RvBase::RvBase() {}
 inline RvBase::RvBase(Errata const &errata) : _errata(errata) {}
+inline RvBase::RvBase(Errata &&errata) : _errata(std::move(errata)) {}
 inline bool
 RvBase::isOK() const
 {
@@ -958,6 +984,7 @@
 template <typename T> Rv<T>::Rv() : _result() {}
 template <typename T> Rv<T>::Rv(Result const &r) : _result(r) {}
 template <typename T> Rv<T>::Rv(Result const &r, Errata const &errata) : super(errata), _result(r) {}
+template <typename T> Rv<T>::Rv(Errata &&errata) : super(std::move(errata)) {}
 template <typename T> Rv<T>::operator Result const &() const
 {
   return _result;
diff --git a/include/tscore/Filenames.h b/include/tscore/Filenames.h
index 60d8441..24c3edf 100644
--- a/include/tscore/Filenames.h
+++ b/include/tscore/Filenames.h
@@ -41,10 +41,12 @@
   constexpr const char *SSL_MULTICERT = "ssl_multicert.config";
   constexpr const char *SPLITDNS      = "splitdns.config";
   constexpr const char *SNI           = "sni.yaml";
+  constexpr const char *JSONRPC       = "jsonrpc.yaml";
 
   ///////////////////////////////////////////////////////////////////
   // Various other file names
   constexpr const char *RECORDS_STATS = "records.snap";
+  constexpr const char *HOST_RECORDS  = "host_records.yaml";
 
 } // namespace filename
 } // namespace ts
diff --git a/include/tscore/MemArena.h b/include/tscore/MemArena.h
index 0b868c8..c64dbe6 100644
--- a/include/tscore/MemArena.h
+++ b/include/tscore/MemArena.h
@@ -27,8 +27,9 @@
 #include <mutex>
 #include <memory>
 #include <utility>
-#include "tscpp/util/MemSpan.h"
-#include "tscore/Scalar.h"
+#include "ink_assert.h"
+#include "swoc/MemSpan.h"
+#include "swoc/Scalar.h"
 #include "tscore/IntrusivePtr.h"
 
 /// Apache Traffic Server commons.
@@ -78,14 +79,14 @@
     size_t remaining() const;
 
     /// Span of unallocated storage.
-    MemSpan<void> remnant();
+    swoc::MemSpan<void> remnant();
 
     /** Allocate @a n bytes from this block.
      *
      * @param n Number of bytes to allocate.
      * @return The span of memory allocated.
      */
-    MemSpan<void> alloc(size_t n);
+    swoc::MemSpan<void> alloc(size_t n);
 
     /** Check if the byte at address @a ptr is in this block.
      *
@@ -127,9 +128,9 @@
       @a n bytes.
 
       @param n number of bytes to allocate.
-      @return a MemSpan of the allocated memory.
+      @return a swoc::MemSpan of the allocated memory.
    */
-  MemSpan<void> alloc(size_t n);
+  swoc::MemSpan<void> alloc(size_t n);
 
   /** Allocate and initialize a block of memory.
 
@@ -183,7 +184,7 @@
   size_t remaining() const;
 
   /// @returns the remaining contiguous space in the active generation.
-  MemSpan<void> remnant() const;
+  swoc::MemSpan<void> remnant() const;
 
   /// @returns the total number of bytes allocated within the arena.
   size_t allocated_size() const;
@@ -208,12 +209,12 @@
    */
   BlockPtr make_block(size_t n);
 
-  using Page      = ts::Scalar<4096>; ///< Size for rounding block sizes.
-  using Paragraph = ts::Scalar<16>;   ///< Minimum unit of memory allocation.
+  using Page      = swoc::Scalar<4096>; ///< Size for rounding block sizes.
+  using Paragraph = swoc::Scalar<16>;   ///< Minimum unit of memory allocation.
 
   static constexpr size_t ALLOC_HEADER_SIZE = 16; ///< Guess of overhead of @c malloc
   /// Initial block size to allocate if not specified via API.
-  static constexpr size_t DEFAULT_BLOCK_SIZE = Page::SCALE - Paragraph{round_up(ALLOC_HEADER_SIZE + sizeof(Block))};
+  static constexpr size_t DEFAULT_BLOCK_SIZE = Page::SCALE - Paragraph{swoc::round_up(ALLOC_HEADER_SIZE + sizeof(Block))};
 
   size_t _active_allocated = 0; ///< Total allocations in the active generation.
   size_t _active_reserved  = 0; ///< Total current reserved memory.
@@ -259,11 +260,11 @@
   return size - allocated;
 }
 
-inline MemSpan<void>
+inline swoc::MemSpan<void>
 MemArena::Block::alloc(size_t n)
 {
   ink_assert(n <= this->remaining());
-  MemSpan<void> zret = this->remnant().prefix(n);
+  swoc::MemSpan<void> zret = this->remnant().prefix(n);
   allocated += n;
   return zret;
 }
@@ -277,7 +278,7 @@
 
 inline MemArena::MemArena(size_t n) : _reserve_hint(n) {}
 
-inline MemSpan<void>
+inline swoc::MemSpan<void>
 MemArena::Block::remnant()
 {
   return {this->data() + allocated, this->remaining()};
@@ -301,10 +302,10 @@
   return _active ? _active->remaining() : 0;
 }
 
-inline MemSpan<void>
+inline swoc::MemSpan<void>
 MemArena::remnant() const
 {
-  return _active ? _active->remnant() : MemSpan<void>{};
+  return _active ? _active->remnant() : swoc::MemSpan<void>{};
 }
 
 inline size_t
diff --git a/include/tscore/Ptr.h b/include/tscore/Ptr.h
index 825320a..c2ff69a 100644
--- a/include/tscore/Ptr.h
+++ b/include/tscore/Ptr.h
@@ -23,8 +23,7 @@
 
 #pragma once
 
-#include "tscore/ink_atomic.h"
-
+#include <atomic>
 #include <cstddef>
 
 ////////////////////////////////////////////////////////////////////
@@ -47,28 +46,23 @@
 {
 public:
   RefCountObj() {}
-  RefCountObj(const RefCountObj &) {}
-
+  RefCountObj(const RefCountObj &) = delete;
   ~RefCountObj() override {}
-  RefCountObj &
-  operator=(const RefCountObj &s)
-  {
-    (void)s;
-    return (*this);
-  }
+
+  RefCountObj &operator=(const RefCountObj &) = delete;
 
   // Increment the reference count, returning the new count.
   int
   refcount_inc()
   {
-    return ink_atomic_increment((int *)&m_refcount, 1) + 1;
+    return ++m_refcount;
   }
 
   // Decrement the reference count, returning the new count.
   int
   refcount_dec()
   {
-    return ink_atomic_increment((int *)&m_refcount, -1) - 1;
+    return --m_refcount;
   }
 
   int
@@ -84,7 +78,7 @@
   }
 
 private:
-  int m_refcount = 0;
+  std::atomic<int> m_refcount = 0;
 };
 
 ////////////////////////////////////////////////////////////////////////
diff --git a/include/tscore/Scalar.h b/include/tscore/Scalar.h
deleted file mode 100644
index 8239316..0000000
--- a/include/tscore/Scalar.h
+++ /dev/null
@@ -1,993 +0,0 @@
-/** @file
-
-  Scaled integral values.
-
-  In many situations it is desirable to define scaling factors or base units (a "metric"). This template
-  enables this to be done in a type and scaling safe manner where the defined factors carry their scaling
-  information as part of the type.
-
-  @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.
- */
-
-#pragma once
-
-#include <cstdint>
-#include <ratio>
-#include <ostream>
-#include <type_traits>
-#include "tscore/BufferWriter.h"
-
-namespace ts
-{
-namespace tag
-{
-  struct generic;
-}
-
-template <intmax_t N, typename C, typename T> class Scalar;
-
-namespace detail
-{
-  // @internal - although these conversion methods look bulky, in practice they compile down to
-  // very small amounts of code due to the conditions being compile time constant - the non-taken
-  // clauses are dead code and eliminated by the compiler.
-
-  // The general case where neither N nor S are a multiple of the other seems a bit long but this
-  // minimizes the risk of integer overflow.  I need to validate that under -O2 the compiler will
-  // only do 1 division to get both the quotient and remainder for (n/N) and (n%N). In cases where
-  // N,S are powers of 2 I have verified recent GNU compilers will optimize to bit operations.
-
-  /// Convert a count @a c that is scale @s S to the corresponding count for scale @c N
-  template <intmax_t N, intmax_t S, typename C>
-  C
-  scale_conversion_round_up(C c)
-  {
-    typedef std::ratio<N, S> R;
-    if (N == S) {
-      return c;
-    } else if (R::den == 1) {
-      return c / R::num + (0 != c % R::num); // N is a multiple of S.
-    } 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));
-    }
-  }
-
-  /// Convert a count @a c that is scale @s S to scale @c N
-  template <intmax_t N, intmax_t S, typename C>
-  C
-  scale_conversion_round_down(C c)
-  {
-    typedef std::ratio<N, S> R;
-    if (N == S) {
-      return c;
-    } else if (R::den == 1) {
-      return c / R::num; // N = k S
-    } 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;
-    }
-  }
-
-  /* Helper classes for @c Scalar
-
-     These wrap values to capture extra information for @c Scalar methods. This includes whether to
-     round up or down when converting and, when the wrapped data is also a @c Scalar, the scale.
-
-     These are not intended for direct use but by the @c round_up and @c round_down free functions
-     which capture the information about the argument and construct an instance of one of these
-     classes to pass it on to a @c Scalar method.
-
-     Scale conversions between @c Scalar instances are handled in these classes via the templated
-     methods @c scale_conversion_round_up and @c scale_conversion_round_down.
-
-     Conversions between scales and types for the scalar helpers is done inside the helper classes
-     and a user type conversion operator exists so the helper can be converted by the compiler to
-     the correct type. For the units bases conversion this is done in @c Scalar because the
-     generality of the needed conversion is too broad to be easily used. It can be done but there is
-     some ugliness due to the fact that in some cases two user conversions which is difficult to
-     deal with. I have tried it both ways and overall this seems a cleaner implementation.
-
-     Much of this is driven by the fact that the assignment operator, in some cases, can not be
-     templated and therefore to have a nice interface for assignment this split is needed.
-
-     Note - the key point is the actual conversion is not done when the wrapper instance is created
-     but when the wrapper instance is assigned. That is what enables the conversion to be done in
-     the context of the destination, which is not otherwise possible.
-   */
-
-  // Unit value, to be rounded up.
-  template <typename C> struct scalar_unit_round_up_t {
-    C _n;
-    template <intmax_t N, typename I>
-    constexpr I
-    scale() const
-    {
-      return static_cast<I>(_n / N + (0 != (_n % N)));
-    }
-  };
-  // Unit value, to be rounded down.
-  template <typename C> struct scalar_unit_round_down_t {
-    C _n;
-    //    template <typename I> operator scalar_unit_round_down_t<I>() { return {static_cast<I>(_n)}; }
-    template <intmax_t N, typename I>
-    constexpr I
-    scale() const
-    {
-      return static_cast<I>(_n / N);
-    }
-  };
-  // 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
-    {
-      return Scalar<S, I, T>(scale_conversion_round_up<S, N>(_n));
-    }
-  };
-  // 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
-    {
-      return Scalar<S, I, T>(scale_conversion_round_down<S, N>(_n));
-    }
-  };
-} // namespace detail
-
-/// Mark a unit value to be scaled, rounding down.
-template <typename C>
-constexpr detail::scalar_unit_round_up_t<C>
-round_up(C n)
-{
-  return {n};
-}
-
-/// Mark a @c Scalar value to be scaled, rounding up.
-template <intmax_t N, typename C, typename T>
-constexpr detail::scalar_round_up_t<N, C, T>
-round_up(Scalar<N, C, T> v)
-{
-  return {v.count()};
-}
-
-/// Mark a unit value to be scaled, rounding down.
-template <typename C>
-constexpr detail::scalar_unit_round_down_t<C>
-round_down(C n)
-{
-  return {n};
-}
-
-/// Mark a @c Scalar value, to be rounded down.
-template <intmax_t N, typename C, typename T>
-constexpr detail::scalar_round_down_t<N, C, T>
-round_down(Scalar<N, C, T> v)
-{
-  return {v.count()};
-}
-
-/** A class to hold scaled values.
-
-    Instances of this class have a @a count and a @a scale. The "value" of the instance is @a
-    count * @a scale.  The scale is stored in the compiler in the class symbol table and so only
-    the count is a run time value. An instance with a large scale can be assign to an instance
-    with a smaller scale and the conversion is done automatically. Conversions from a smaller to
-    larger scale must be explicit using @c round_up and @c round_down. This prevents
-    inadvertent changes in value. Because the scales are not the same these conversions can be
-    lossy and the two conversions determine whether, in such a case, the result should be rounded
-    up or down to the nearest scale value.
-
-    @a N sets the scale. @a C is the type used to hold the count, which is in units of @a N.
-
-    @a T is a "tag" type which is used only to distinguish the base metric for the scale. Scalar
-    types that have different tags are not interoperable although they can be converted manually by
-    converting to units and then explicitly constructing a new Scalar instance. This is by
-    design. This can be ignored - if not specified then it defaults to a "generic" tag. The type can
-    be (and usually is) defined in name only).
-
-    @note This is modeled somewhat on @c std::chrono and serves a similar function for different
-    and simpler cases (where the ratio is always an integer, never a fraction).
-
-    @see round_up
-    @see round_down
- */
-template <intmax_t N, typename C = int, typename T = tag::generic> class Scalar
-{
-  typedef Scalar self; ///< Self reference type.
-
-public:
-  /// Scaling factor - make it external accessible.
-  constexpr static intmax_t SCALE = N;
-  typedef C Counter; ///< Type used to hold the count.
-  typedef T Tag;     ///< Make tag accessible.
-
-  static_assert(N > 0, "The scaling factor (1st template argument) must be a positive integer");
-  static_assert(std::is_integral<C>::value, "The counter type (2nd template argument) must be an integral type");
-
-  constexpr Scalar(); ///< Default constructor.
-  ///< Construct to have @a n scaled units.
-  explicit constexpr Scalar(Counter n);
-  /// Copy constructor.
-  constexpr Scalar(self const &that); /// Copy constructor.
-  /// Copy constructor for same scale.
-  template <typename I> constexpr Scalar(Scalar<N, I, T> const &that);
-  /// Direct conversion constructor.
-  /// @note Requires that @c S be an integer multiple of @c SCALE.
-  template <intmax_t S, typename I> constexpr Scalar(Scalar<S, I, T> const &that);
-  /// Conversion constructor.
-  constexpr Scalar(detail::scalar_round_up_t<N, C, T> const &that);
-  /// Conversion constructor.
-  constexpr Scalar(detail::scalar_round_down_t<N, C, T> const &that);
-  /// Conversion constructor.
-  template <typename I> constexpr Scalar(detail::scalar_unit_round_up_t<I> v);
-  /// Conversion constructor.
-  template <typename I> constexpr Scalar(detail::scalar_unit_round_down_t<I> v);
-
-  /// Assignment operator.
-  /// The value @a that is scaled appropriately.
-  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
-  /// the @c round_up or @c round_down must be used to indicate the rounding direction.
-  template <intmax_t S, typename I> self &operator=(Scalar<S, I, T> const &that);
-  /// Assignment from same scale.
-  self &operator=(self const &that);
-  // Conversion assignments.
-  template <typename I> self &operator=(detail::scalar_unit_round_up_t<I> n);
-  template <typename I> self &operator=(detail::scalar_unit_round_down_t<I> n);
-  self &operator                      =(detail::scalar_round_up_t<N, C, T> v);
-  self &operator                      =(detail::scalar_round_down_t<N, C, T> v);
-
-  /// Direct assignment.
-  /// The count is set to @a n.
-  self &assign(Counter n);
-  /// The value @a that is scaled appropriately.
-  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
-  /// the @c round_up or @c round_down must be used to indicate the rounding direction.
-  template <intmax_t S, typename I> self &assign(Scalar<S, I, T> const &that);
-  // Conversion assignments.
-  template <typename I> self &assign(detail::scalar_unit_round_up_t<I> n);
-  template <typename I> self &assign(detail::scalar_unit_round_down_t<I> n);
-  self &assign(detail::scalar_round_up_t<N, C, T> v);
-  self &assign(detail::scalar_round_down_t<N, C, T> v);
-
-  /// The number of scale units.
-  constexpr Counter count() const;
-  /// The scaled value.
-  constexpr intmax_t value() const;
-  /// User conversion to scaled value.
-  constexpr operator intmax_t() const;
-
-  /// Addition operator.
-  /// The value is scaled from @a that to @a this.
-  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
-  /// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
-  self &operator+=(self const &that);
-  template <intmax_t S, typename I> self &operator+=(Scalar<S, I, T> const &that);
-  template <typename I> self &operator+=(detail::scalar_unit_round_up_t<I> n);
-  template <typename I> self &operator+=(detail::scalar_unit_round_down_t<I> n);
-  self &operator+=(detail::scalar_round_up_t<N, C, T> v);
-  self &operator+=(detail::scalar_round_down_t<N, C, T> v);
-
-  /// Increment - increase count by 1.
-  self &operator++();
-  /// Increment - increase count by 1.
-  self operator++(int);
-  /// Decrement - decrease count by 1.
-  self &operator--();
-  /// Decrement - decrease count by 1.
-  self operator--(int);
-  /// Increment by @a n.
-  self &inc(Counter n);
-  /// Decrement by @a n.
-  self &dec(Counter n);
-
-  /// Subtraction operator.
-  /// The value is scaled from @a that to @a this.
-  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
-  /// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
-  self &operator-=(self const &that);
-  template <intmax_t S, typename I> self &operator-=(Scalar<S, I, T> const &that);
-  template <typename I> self &operator-=(detail::scalar_unit_round_up_t<I> n);
-  template <typename I> self &operator-=(detail::scalar_unit_round_down_t<I> n);
-  self &operator-=(detail::scalar_round_up_t<N, C, T> v);
-  self &operator-=(detail::scalar_round_down_t<N, C, T> v);
-
-  /// Multiplication - multiple the count by @a n.
-  self &operator*=(C n);
-
-  /// Division - divide (rounding down) the count by @a n.
-  self &operator/=(C n);
-
-  /// Utility overload of the function operator to create instances at the same scale.
-  self operator()(Counter n) const;
-
-  /// Return a value at the same scale with a count increased by @a n.
-  self plus(Counter n) const;
-
-  /// Return a value at the same scale with a count decreased by @a n.
-  self minus(Counter n) const;
-
-  /// Run time access to the scale (template arg @a N).
-  static constexpr intmax_t scale();
-
-protected:
-  Counter _n; ///< Number of scale units.
-};
-
-template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar() : _n() {}
-template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(Counter n) : _n(n) {}
-template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(self 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()))
-{
-}
-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())
-{
-  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)
-{
-}
-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)
-{
-}
-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>())
-{
-}
-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>())
-{
-}
-
-template <intmax_t N, typename C, typename T>
-constexpr auto
-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
-{
-  return _n * SCALE;
-}
-
-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 &
-{
-  _n = n;
-  return *this;
-}
-
-template <intmax_t N, typename C, typename T>
-inline auto
-Scalar<N, C, T>::operator=(self const &that) -> self &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  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;
-  return *this;
-}
-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 &
-{
-  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;
-  return *this;
-}
-
-template <intmax_t N, typename C, typename T>
-constexpr inline intmax_t
-Scalar<N, C, T>::scale()
-{
-  return SCALE;
-}
-
-// --- Compare operators
-// These optimize nicely because if R::num or R::den is 1 the compiler will drop it.
-
-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)
-{
-  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)
-{
-  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)
-{
-  typedef std::ratio<N, S> R;
-  return lhs.count() * R::num <= rhs.count() * R::den;
-}
-
-// 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)
-{
-  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)
-{
-  return rhs <= lhs;
-}
-
-// Arithmetic operators
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator+=(self const &that) -> self &
-{
-  _n += that._n;
-  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 &
-{
-  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;
-  return *this;
-}
-
-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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  return Scalar<N, C, T>(lhs) += rhs._n;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator-=(self const &that) -> self &
-{
-  _n -= that._n;
-  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 &
-{
-  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;
-  return *this;
-}
-
-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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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 &
-{
-  _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
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  return Scalar<N, C, T>(lhs) -= rhs._n;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator++() -> self &
-{
-  ++_n;
-  return *this;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator++(int) -> self
-{
-  self zret(*this);
-  ++_n;
-  return zret;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator--() -> self &
-{
-  --_n;
-  return *this;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator--(int) -> self
-{
-  self zret(*this);
-  --_n;
-  return zret;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::inc(Counter n) -> self &
-{
-  _n += n;
-  return *this;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::dec(Counter n) -> self &
-{
-  _n -= n;
-  return *this;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator*=(C n) -> self &
-{
-  _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)
-{
-  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)
-{
-  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)
-{
-  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)
-{
-  return Scalar<N, C, T>(rhs) *= 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)
-{
-  return Scalar<N, int>(rhs) *= n;
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::operator/=(C n) -> self &
-{
-  _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
-{
-  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)
-{
-  static_assert(std::is_integral<I>::value, "Scalar division 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
-{
-  return self{n};
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::plus(Counter n) const -> self
-{
-  return {_n + n};
-}
-
-template <intmax_t N, typename C, typename T>
-auto
-Scalar<N, C, T>::minus(Counter n) const -> self
-{
-  return {_n - n};
-}
-
-template <intmax_t N, typename C>
-C
-round_up(C value)
-{
-  return N * detail::scale_conversion_round_up<N, 1>(value);
-}
-
-template <intmax_t N, typename C>
-C
-round_down(C value)
-{
-  return N * detail::scale_conversion_round_down<N, 1>(value);
-}
-
-namespace detail
-{
-  // These classes exist only to create distinguishable overloads.
-  struct tag_label_A {
-  };
-  struct tag_label_B : public tag_label_A {
-  };
-  // The purpose is to print a label for a tagged type only if the tag class defines a member that
-  // is the label.  This creates a base function that always works and does nothing. The second
-  // function creates an overload if the tag class has a member named 'label' that has an stream IO
-  // output operator. When invoked with a second argument of B then the second overload exists and
-  // is used, otherwise only the first exists and that is used. The critical technology is the use
-  // of 'auto' and 'decltype' which effectively checks if the code inside 'decltype' compiles.
-  template <typename T>
-  inline std::ostream &
-  tag_label(std::ostream &s, tag_label_A const &)
-  {
-    return s;
-  }
-  template <typename T>
-  inline BufferWriter &
-  tag_label(BufferWriter &w, BWFSpec const &, tag_label_A const &)
-  {
-    return w;
-  }
-  template <typename T>
-  inline auto
-  tag_label(std::ostream &s, tag_label_B const &) -> decltype(s << T::label, s)
-  {
-    return s << T::label;
-  }
-  template <typename T>
-  inline auto
-  tag_label(BufferWriter &w, BWFSpec const &spec, tag_label_B const &) -> decltype(bwformat(w, spec, T::label), w)
-  {
-    return bwformat(w, spec, T::label);
-  }
-} // namespace detail
-
-template <intmax_t N, typename C, typename T>
-BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, Scalar<N, C, T> const &x)
-{
-  static constexpr ts::detail::tag_label_B b{};
-  bwformat(w, spec, x.value());
-  return ts::detail::tag_label<T>(w, spec, b);
-}
-
-} // namespace ts
-
-namespace std
-{
-template <intmax_t N, typename C, typename T>
-ostream &
-operator<<(ostream &s, ts::Scalar<N, C, T> const &x)
-{
-  static ts::detail::tag_label_B b; // Can't be const or the compiler gets upset.
-  s << x.value();
-  return ts::detail::tag_label<T>(s, b);
-}
-
-/// Compute common type of two scalars.
-/// In `std` to overload the base definition. This yields a type that has the common type of the
-/// counter type and a scale that is the GCF of the input scales.
-template <intmax_t N, typename C, intmax_t S, typename I, typename T> struct common_type<ts::Scalar<N, C, T>, ts::Scalar<S, I, T>> {
-  typedef std::ratio<N, S> R;
-  typedef ts::Scalar<N / R::num, typename common_type<C, I>::type, T> type;
-};
-} // namespace std
diff --git a/include/tscore/ink_defs.h b/include/tscore/ink_defs.h
index 0202649..2fe7351 100644
--- a/include/tscore/ink_defs.h
+++ b/include/tscore/ink_defs.h
@@ -91,6 +91,8 @@
 #define unlikely(x) __builtin_expect(!!(x), 0)
 #endif
 
+#define MAX_ALPN_STRING 30
+
 /* Variables
  */
 extern int off;
diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index 332fa03..1c70107 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -72,8 +72,8 @@
 extern const std::string_view IP_PROTO_TAG_HTTP_2_0;
 extern const std::string_view IP_PROTO_TAG_HTTP_QUIC;
 extern const std::string_view IP_PROTO_TAG_HTTP_3;
-extern const std::string_view IP_PROTO_TAG_HTTP_QUIC_D27;
-extern const std::string_view IP_PROTO_TAG_HTTP_3_D27;
+extern const std::string_view IP_PROTO_TAG_HTTP_QUIC_D29;
+extern const std::string_view IP_PROTO_TAG_HTTP_3_D29;
 
 struct IpAddr; // forward declare.
 
diff --git a/include/tscpp/api/TransformationPlugin.h b/include/tscpp/api/TransformationPlugin.h
index e9b1a7f..c77e33c 100644
--- a/include/tscpp/api/TransformationPlugin.h
+++ b/include/tscpp/api/TransformationPlugin.h
@@ -84,10 +84,12 @@
    * The available types of Transformations.
    */
   enum Type {
-    REQUEST_TRANSFORMATION = 0, /**< Transform the Request body content */
-    RESPONSE_TRANSFORMATION,    /**< Transform the Response body content */
-    SINK_TRANSFORMATION         /**< Sink transformation, meaning you get a separate stream of the Response
-                                     body content that does not get hooked up to a downstream input */
+    REQUEST_TRANSFORMATION = 0,          /**< Transform the Request body content */
+    RESPONSE_TRANSFORMATION,             /**< Transform the Response body content */
+    CLIENT_RESPONSE_SINK_TRANSFORMATION, /**< Sink transformation, meaning you get a separate stream of the Response
+                                              body content that does not get hooked up to a downstream input */
+    CLIENT_REQUEST_SINK_TRANSFORMATION,  /**< Sink transformation, meaning you get a separate stream of the Request
+                                              body content that does not get hooked up to a downstream input */
   };
 
   /**
diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am
index a1d18fd..bde4eb3 100644
--- a/include/tscpp/util/Makefile.am
+++ b/include/tscpp/util/Makefile.am
@@ -19,10 +19,11 @@
 library_includedir=$(includedir)/tscpp/util
 
 library_include_HEADERS = \
+        ts_diag_levels.h \
+        ts_ip.h \
 	IntrusiveDList.h \
 	LocalBuffer.h \
 	PostScript.h \
 	Strerror.h \
-	string_view_util.h \
 	TextView.h \
 	TsSharedMutex.h
diff --git a/include/tscpp/util/MemSpan.h b/include/tscpp/util/MemSpan.h
deleted file mode 100644
index c234265..0000000
--- a/include/tscpp/util/MemSpan.h
+++ /dev/null
@@ -1,877 +0,0 @@
-/** @file
-
-   Spans of writable memory. This is similar but independently developed from @c std::span. The goal
-   is to provide convenient handling for chunks of memory. These chunks can be treated as arrays of
-   arbitrary types via template methods.
-*/
-
-/* 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 <cstring>
-#include <iosfwd>
-#include <iostream>
-#include <cstddef>
-#include <string_view>
-#include <type_traits>
-#include <ratio>
-#include <exception>
-
-namespace ts
-{
-/** A span of contiguous piece of memory.
-
-    A @c MemSpan does not own the memory to which it refers, it is simply a span of part of some
-    (presumably) larger memory object. It acts as a pointer, not a container - copy and assignment
-    change the span, not the memory to which the span refers.
-
-    The purpose is that frequently code needs to work on a specific part of the memory. This can
-    avoid copying or allocation by allocating all needed memory at once and then working with it via
-    instances of this class.
-
- */
-template <typename T> class MemSpan
-{
-  using self_type = MemSpan; ///< Self reference type.
-
-protected:
-  T *_ptr       = nullptr; ///< Pointer to base of memory chunk.
-  size_t _count = 0;       ///< Number of elements.
-
-public:
-  using value_type = T;
-
-  /// Default constructor (empty buffer).
-  constexpr MemSpan() = default;
-
-  /// Copy constructor.
-  constexpr MemSpan(self_type const &that) = default;
-
-  /** Construct from a first element @a start and a @a count of elements.
-   *
-   * @param start First element.
-   * @param count Total number of elements.
-   */
-  constexpr MemSpan(value_type *start, size_t count);
-
-  /** Construct from a half open range [start, last).
-   *
-   * @param start Start of range.
-   * @param last Past end of range.
-   */
-  constexpr MemSpan(value_type *start, value_type *last);
-
-  /** Construct to cover an array.
-   *
-   * @tparam N Number of elements in the array.
-   * @param a The array.
-   */
-  template <size_t N> MemSpan(T (&a)[N]);
-
-  /** Construct from nullptr.
-      This implicitly makes the length 0.
-  */
-  constexpr MemSpan(std::nullptr_t);
-
-  /** Equality.
-
-      Compare the span contents.
-
-      @return @c true if the contents of @a that are the same as the content of @a this,
-      @c false otherwise.
-   */
-  bool operator==(self_type const &that) const;
-
-  /** Identical.
-
-      Check if the spans refer to the same span of memory.
-      @return @c true if @a this and @a that refer to the same span, @c false if not.
-   */
-  bool is_same(self_type const &that) const;
-
-  /** Inequality.
-      @return @c true if @a that does not refer to the same span as @a this,
-      @c false otherwise.
-   */
-  bool operator!=(self_type const &that) const;
-
-  /// Assignment - the span is copied, not the content.
-  self_type &operator=(self_type const &that) = default;
-
-  /// Access element at index @a idx.
-  T &operator[](size_t idx) const;
-
-  /// Check for empty span.
-  /// @return @c true if the span is empty (no contents), @c false otherwise.
-  bool operator!() const;
-
-  /// Check for non-empty span.
-  /// @return @c true if the span contains bytes.
-  explicit operator bool() const;
-
-  /// Check for empty span (no content).
-  /// @see operator bool
-  bool empty() const;
-
-  /// @name Accessors.
-  //@{
-  /// Pointer to the first element in the span.
-  T *begin() const;
-
-  /// Pointer to first element not in the span.
-  T *end() const;
-
-  /// Number of elements in the span
-  size_t count() const;
-
-  /// Number of bytes in the span.
-  size_t size() const;
-
-  /// Pointer to memory in the span.
-  T *data() const;
-
-  /** Make a copy of @a this span on the same memory but of type @a U.
-   *
-   * @tparam U Type for the created span.
-   * @return A @c MemSpan which contains the same memory as instances of @a U.
-   */
-  template <typename U = void> MemSpan<U> rebind() const;
-
-  /// Set the span.
-  /// This is faster but equivalent to constructing a new span with the same
-  /// arguments and assigning it.
-  /// @return @c this.
-  self_type &assign(T *ptr,      ///< Buffer start.
-                    size_t count ///< # of elements.
-  );
-
-  /// Set the span.
-  /// This is faster but equivalent to constructing a new span with the same
-  /// arguments and assigning it.
-  /// @return @c this.
-  self_type &assign(T *first,     ///< First valid element.
-                    T const *last ///< First invalid element.
-  );
-
-  /// Clear the span (become an empty span).
-  self_type &clear();
-
-  /// @return @c true if the byte at @a *p is in the span.
-  bool contains(value_type const *p) const;
-
-  /** Get the initial segment of @a count elements.
-
-      @return An instance that contains the leading @a count elements of @a this.
-  */
-  self_type prefix(size_t count) const;
-
-  /** Shrink the span by removing @a count leading elements.
-   *
-   * @param count The number of elements to remove.
-   * @return @c *this
-   */
-  self_type &remove_prefix(size_t count);
-
-  /** Get the trailing segment of @a count elements.
-   *
-   * @param count Number of elements to retrieve.
-   * @return An instance that contains the trailing @a count elements of @a this.
-   */
-  self_type suffix(size_t count) const;
-
-  /** Shrink the span by removing @a count trailing elements.
-   *
-   * @param count Number of elements to remove.
-   * @return @c *this
-   */
-  self_type &remove_suffix(size_t count);
-
-  /** Return a view of the memory.
-   *
-   * @return A @c string_view covering the span contents.
-   */
-  std::string_view view() const;
-
-  template <typename U> friend class MemSpan;
-};
-
-/** Specialization for void pointers.
- *
- * Key differences:
- *
- * - No subscript operator.
- * - No array initialization.
- * - All other @c MemSpan types implicitly convert to this type.
- *
- * @internal I tried to be clever about the base template but there were too many differences
- * 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>
-{
-  using self_type = MemSpan; ///< Self reference type.
-  template <typename U> friend class MemSpan;
-
-public:
-  using value_type = void; /// Export base type.
-
-protected:
-  value_type *_ptr = nullptr; ///< Pointer to base of memory chunk.
-  size_t _size     = 0;       ///< Number of elements.
-
-public:
-  /// Default constructor (empty buffer).
-  constexpr MemSpan() = default;
-
-  /// Copy constructor.
-  constexpr MemSpan(self_type const &that) = default;
-  MemSpan &operator=(const MemSpan &) = default;
-
-  /** Cross type copy constructor.
-   *
-   * @tparam U Type for source span.
-   * @param that Source span.
-   *
-   * This enables any @c MemSpan to be automatically converted to a void span, just as any pointer
-   * can convert to a void pointer.
-   */
-  template <typename U> constexpr MemSpan(MemSpan<U> const &that);
-
-  /** Construct from a pointer @a start and a size @a n bytes.
-   *
-   * @param start Start of the span.
-   * @param n # of bytes in the span.
-   */
-  constexpr MemSpan(value_type *start, size_t n);
-
-  /** Construct from a half open range of [start, last).
-   *
-   * @param start Start of the range.
-   * @param last Past end of range.
-   */
-  MemSpan(value_type *start, value_type *last);
-
-  /** Construct from nullptr.
-      This implicitly makes the length 0.
-  */
-  constexpr MemSpan(std::nullptr_t);
-
-  /** Equality.
-
-      Compare the span contents.
-
-      @return @c true if the contents of @a that are bytewise the same as the content of @a this,
-      @c false otherwise.
-   */
-  bool operator==(self_type const &that) const;
-
-  /** Identical.
-
-      Check if the spans refer to the same span of memory.
-
-      @return @c true if @a this and @a that refer to the same memory, @c false if not.
-   */
-  bool is_same(self_type const &that) const;
-
-  /** Inequality.
-      @return @c true if @a that does not refer to the same span as @a this,
-      @c false otherwise.
-   */
-  bool operator!=(self_type const &that) const;
-
-  /// Assignment - the span is copied, not the content.
-  /// Any type of @c MemSpan can be assigned to @c MemSpan<void>.
-  template <typename U> self_type &operator=(MemSpan<U> const &that);
-
-  /// Check for empty span.
-  /// @return @c true if the span is empty (no contents), @c false otherwise.
-  bool operator!() const;
-
-  /// Check for non-empty span.
-  /// @return @c true if the span contains bytes.
-  explicit operator bool() const;
-
-  /// Check for empty span (no content).
-  /// @see operator bool
-  bool empty() const;
-
-  /// Number of bytes in the span.
-  size_t size() const;
-
-  /// Pointer to memory in the span.
-  value_type *data() const;
-
-  /// Pointer to memory in the span.
-  value_type *data_end() const;
-
-  /** Create a new span for a different type @a V on the same memory.
-   *
-   * @tparam V Type for the created span.
-   * @return A @c MemSpan which contains the same memory as instances of @a V.
-   */
-  template <typename U> MemSpan<U> rebind() const;
-
-  /// Set the span.
-  /// This is faster but equivalent to constructing a new span with the same
-  /// arguments and assigning it.
-  /// @return @c this.
-  self_type &assign(value_type *ptr, ///< Buffer start.
-                    size_t n         ///< # of bytes
-  );
-
-  /// Set the span.
-  /// This is faster but equivalent to constructing a new span with the same
-  /// arguments and assigning it.
-  /// @return @c this.
-  self_type &assign(value_type *first,     ///< First valid element.
-                    value_type const *last ///< First invalid element.
-  );
-
-  /// Clear the span (become an empty span).
-  self_type &clear();
-
-  /// @return @c true if the byte at @a *ptr is in the span.
-  bool contains(value_type const *ptr) const;
-
-  /** Get the initial segment of @a n bytes.
-
-      @return An instance that contains the leading @a n bytes of @a this.
-  */
-  self_type prefix(size_t n) const;
-
-  /** Shrink the span by removing @a n leading bytes.
-   *
-   * @param count The number of elements to remove.
-   * @return @c *this
-   */
-
-  self_type &remove_prefix(size_t count);
-
-  /** Get the trailing segment of @a n bytes.
-   *
-   * @param n Number of bytes to retrieve.
-   * @return An instance that contains the trailing @a count elements of @a this.
-   */
-  self_type suffix(size_t n) const;
-
-  /** Shrink the span by removing @a n bytes.
-   *
-   * @param n Number of bytes to remove.
-   * @return @c *this
-   */
-  self_type &remove_suffix(size_t n);
-
-  /** Return a view of the memory.
-   *
-   * @return A @c string_view covering the span contents.
-   */
-  std::string_view view() const;
-};
-
-// -- Implementation --
-
-namespace detail
-{
-  /// Support pointer distance calculations for all types, @b include @c <void*>.
-  /// This is useful in templates.
-  inline size_t
-  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)
-  {
-    return last - first;
-  }
-
-  /** Functor to convert span types.
-   *
-   * @tparam T Source span type.
-   * @tparam U Destination span type.
-   *
-   * @internal More void handling. This can't go in @c MemSpan because template specialization is
-   * invalid in class scope and this needs to be specialized for @c void.
-   */
-  template <typename T, typename U> struct is_span_compatible {
-    /// @c true if the size of @a T is an integral multiple of the size of @a U or vice versa.
-    static constexpr bool value = std::ratio<sizeof(T), sizeof(U)>::num == 1 || std::ratio<sizeof(U), sizeof(T)>::num == 1;
-    /** Compute the new size in units of @c sizeof(U).
-     *
-     * @param size Size in bytes.
-     * @return Size in units of @c sizeof(U).
-     *
-     * The critical part of this is the @c static_assert that guarantees the result is an integral
-     * number of instances of @a U.
-     */
-    static size_t count(size_t size);
-  };
-
-  template <typename T, typename U>
-  size_t
-  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);
-  }
-
-  /// @cond INTERNAL_DETAIL
-  // Must specialize for rebinding to @c void because @c sizeof doesn't work. Rebinding from @c void
-  // is handled by the @c MemSpan<void>::rebind specialization and doesn't use this mechanism.
-  template <typename T> struct is_span_compatible<T, void> {
-    static constexpr bool value = true;
-    static size_t count(size_t size);
-  };
-
-  template <typename T>
-  size_t
-  is_span_compatible<T, void>::count(size_t size)
-  {
-    return size;
-  }
-  /// @endcond
-
-} // namespace detail
-
-// --- Standard memory operations ---
-
-template <typename T>
-int
-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()) {
-    zret = 1;
-  } 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.
-    zret = r;
-  }
-
-  return zret;
-}
-
-using std::memcmp;
-
-template <typename T>
-T *
-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)
-{
-  return static_cast<T *>(std::memcpy(dst.data(), src, dst.size()));
-}
-
-template <typename T>
-T *
-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)
-{
-  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)
-{
-  return std::memcpy(span.data(), view.data(), std::min(view.size(), view.size()));
-}
-
-using std::memcpy;
-using std::memcpy;
-
-template <typename T>
-inline MemSpan<T> const &
-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)
-{
-  std::memset(dst.data(), c, dst.size());
-  return dst;
-}
-
-inline MemSpan<unsigned char> const &
-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)
-{
-  std::memset(dst.data(), c, dst.size());
-  return dst;
-}
-
-using std::memset;
-
-// --- MemSpan<T> ---
-
-template <typename T> constexpr MemSpan<T>::MemSpan(T *ptr, size_t count) : _ptr{ptr}, _count{count} {}
-
-template <typename T> constexpr MemSpan<T>::MemSpan(T *first, T *last) : _ptr{first}, _count{detail::ptr_distance(first, last)} {}
-
-template <typename T> template <size_t N> MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{a}, _count{N} {}
-
-template <typename T> constexpr MemSpan<T>::MemSpan(std::nullptr_t) {}
-
-template <typename T>
-MemSpan<T> &
-MemSpan<T>::assign(T *ptr, size_t count)
-{
-  _ptr   = ptr;
-  _count = count;
-  return *this;
-}
-
-template <typename T>
-MemSpan<T> &
-MemSpan<T>::assign(T *first, T const *last)
-{
-  _ptr   = first;
-  _count = detail::ptr_distance(first, last);
-  return *this;
-}
-
-template <typename T>
-MemSpan<T> &
-MemSpan<T>::clear()
-{
-  _ptr   = nullptr;
-  _count = 0;
-  return *this;
-}
-
-template <typename T>
-bool
-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
-{
-  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
-{
-  return !(*this == that);
-}
-
-template <typename T>
-bool
-MemSpan<T>::operator!() const
-{
-  return _count == 0;
-}
-
-template <typename T> MemSpan<T>::operator bool() const
-{
-  return _count != 0;
-}
-
-template <typename T>
-bool
-MemSpan<T>::empty() const
-{
-  return _count == 0;
-}
-
-template <typename T>
-T *
-MemSpan<T>::begin() const
-{
-  return _ptr;
-}
-
-template <typename T>
-T *
-MemSpan<T>::data() const
-{
-  return _ptr;
-}
-
-template <typename T>
-T *
-MemSpan<T>::end() const
-{
-  return _ptr + _count;
-}
-
-template <typename T>
-T &
-MemSpan<T>::operator[](size_t idx) const
-{
-  return _ptr[idx];
-}
-
-template <typename T>
-size_t
-MemSpan<T>::count() const
-{
-  return _count;
-}
-
-template <typename T>
-size_t
-MemSpan<T>::size() const
-{
-  return _count * sizeof(T);
-}
-
-template <typename T>
-bool
-MemSpan<T>::contains(T const *ptr) const
-{
-  return _ptr <= ptr && ptr < _ptr + _count;
-}
-
-template <typename T>
-auto
-MemSpan<T>::prefix(size_t count) const -> self_type
-{
-  return {_ptr, std::min(count, _count)};
-}
-
-template <typename T>
-auto
-MemSpan<T>::remove_prefix(size_t count) -> self_type &
-{
-  count = std::min(_count, count);
-  _count -= count;
-  _ptr += count;
-  return *this;
-}
-
-template <typename T>
-auto
-MemSpan<T>::suffix(size_t count) const -> self_type
-{
-  count = std::min(_count, count);
-  return {(_ptr + _count) - count, count};
-}
-
-template <typename T>
-MemSpan<T> &
-MemSpan<T>::remove_suffix(size_t count)
-{
-  _count -= std::min(count, _count);
-  return *this;
-}
-
-template <typename T>
-template <typename U>
-MemSpan<U>
-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())};
-}
-
-template <typename T>
-std::string_view
-MemSpan<T>::view() const
-{
-  return {static_cast<const char *>(_ptr), this->size()};
-}
-
-// --- void specialization ---
-
-template <typename U> constexpr MemSpan<void>::MemSpan(MemSpan<U> const &that) : _ptr(that._ptr), _size(that.size()) {}
-
-inline constexpr MemSpan<void>::MemSpan(value_type *ptr, size_t n) : _ptr{ptr}, _size{n} {}
-
-inline MemSpan<void>::MemSpan(value_type *first, value_type *last) : _ptr{first}, _size{detail::ptr_distance(first, last)} {}
-
-inline constexpr MemSpan<void>::MemSpan(std::nullptr_t) {}
-
-inline MemSpan<void> &
-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)
-{
-  _ptr  = first;
-  _size = detail::ptr_distance(first, last);
-  return *this;
-}
-
-inline MemSpan<void> &
-MemSpan<void>::clear()
-{
-  _ptr  = nullptr;
-  _size = 0;
-  return *this;
-}
-
-inline bool
-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
-{
-  return _size == that._size && (_ptr == that._ptr || 0 == memcmp(_ptr, that._ptr, _size));
-}
-
-inline bool
-MemSpan<void>::operator!=(self_type const &that) const
-{
-  return !(*this == that);
-}
-
-inline bool
-MemSpan<void>::operator!() const
-{
-  return _size == 0;
-}
-
-inline MemSpan<void>::operator bool() const
-{
-  return _size != 0;
-}
-
-inline bool
-MemSpan<void>::empty() const
-{
-  return _size == 0;
-}
-
-inline void *
-MemSpan<void>::data() const
-{
-  return _ptr;
-}
-
-inline void *
-MemSpan<void>::data_end() const
-{
-  return static_cast<char *>(_ptr) + _size;
-}
-
-inline size_t
-MemSpan<void>::size() const
-{
-  return _size;
-}
-
-template <typename U>
-auto
-MemSpan<void>::operator=(MemSpan<U> const &that) -> self_type &
-{
-  _ptr  = that._ptr;
-  _size = that.size();
-  return *this;
-}
-
-inline bool
-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
-{
-  return {_ptr, std::min(n, _size)};
-}
-
-inline MemSpan<void> &
-MemSpan<void>::remove_prefix(size_t n)
-{
-  n = std::max(_size, n);
-  _size -= n;
-  _ptr = static_cast<char *>(_ptr) + n;
-  return *this;
-}
-
-inline MemSpan<void>
-MemSpan<void>::suffix(size_t count) const
-{
-  count = std::max(count, _size);
-  return {static_cast<char *>(this->data_end()) - count, size_t(count)};
-}
-
-inline MemSpan<void> &
-MemSpan<void>::remove_suffix(size_t count)
-{
-  _size -= std::max(count, _size);
-  return *this;
-}
-
-template <typename U>
-MemSpan<U>
-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
-{
-  return *this;
-}
-
-inline std::string_view
-MemSpan<void>::view() const
-{
-  return {static_cast<char const *>(_ptr), _size};
-}
-
-} // namespace ts
diff --git a/include/tscpp/util/TextView.h b/include/tscpp/util/TextView.h
index da524ce..15638b7 100644
--- a/include/tscpp/util/TextView.h
+++ b/include/tscpp/util/TextView.h
@@ -35,7 +35,7 @@
 #include <limits>
 #include <cstdint>
 
-#include "tscpp/util/string_view_util.h"
+#include "swoc/string_view_util.h"
 
 namespace ts
 {
diff --git a/include/tscpp/util/ts_diag_levels.h b/include/tscpp/util/ts_diag_levels.h
new file mode 100644
index 0000000..46536ea
--- /dev/null
+++ b/include/tscpp/util/ts_diag_levels.h
@@ -0,0 +1,38 @@
+/** @file Diagnostic definitions and functions.
+
+@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.
+*/
+
+#pragma once
+
+/// Severity level for diagnostics.
+/// @internal Used as array indices, do not renumber or rearrange.
+/// @see DiagTypes.h
+enum DiagsLevel {
+  DL_Diag = 0,  // process does not die
+  DL_Debug,     // process does not die
+  DL_Status,    // process does not die
+  DL_Note,      // process does not die
+  DL_Warning,   // process does not die
+  DL_Error,     // process does not die
+  DL_Fatal,     // causes process termination
+  DL_Alert,     // causes process termination
+  DL_Emergency, // causes process termination, exits with UNRECOVERABLE_EXIT
+  DL_Undefined  // must be last, used for size!
+};
diff --git a/include/tscpp/util/ts_errata.h b/include/tscpp/util/ts_errata.h
new file mode 100644
index 0000000..729a567
--- /dev/null
+++ b/include/tscpp/util/ts_errata.h
@@ -0,0 +1,40 @@
+/** @file Diagnostic definitions and functions.
+
+@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.
+*/
+
+#pragma once
+
+#include "tscpp/util/ts_diag_levels.h"
+#include "swoc/TextView.h"
+#include "swoc/Errata.h"
+
+static constexpr swoc::Errata::Severity ERRATA_DIAG{DL_Diag};
+static constexpr swoc::Errata::Severity ERRATA_DEBUG{DL_Debug};
+static constexpr swoc::Errata::Severity ERRATA_STATUS{DL_Status};
+static constexpr swoc::Errata::Severity ERRATA_NOTE{DL_Note};
+static constexpr swoc::Errata::Severity ERRATA_WARN{DL_Warning};
+static constexpr swoc::Errata::Severity ERRATA_ERROR{DL_Error};
+static constexpr swoc::Errata::Severity ERRATA_FATAL{DL_Fatal};
+static constexpr swoc::Errata::Severity ERRATA_ALERT{DL_Alert};
+static constexpr swoc::Errata::Severity ERRATA_EMERGENCY{DL_Emergency};
+
+// This is treated as an array so must numerically match with @c DiagsLevel
+static constexpr std::array<swoc::TextView, 9> Severity_Names{
+  {"Diag", "Debug", "Status", "Note", "Warn", "Error", "Fatal", "Alert", "Emergency"}};
diff --git a/include/tscpp/util/ts_ip.h b/include/tscpp/util/ts_ip.h
new file mode 100644
index 0000000..92dbd05
--- /dev/null
+++ b/include/tscpp/util/ts_ip.h
@@ -0,0 +1,459 @@
+/** @file
+
+  IP address handling support.
+
+  Built on top of libswoc IP networking support to provide utilities specialized for ATS.
+
+  @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.
+*/
+
+#pragma once
+
+#include <optional>
+
+#include "swoc/swoc_ip.h"
+
+namespace ts
+{
+/// Pair of addresses, each optional.
+/// Used in situations where both an IPv4 and IPv6 may be needed.
+class IPAddrPair
+{
+public:
+  using self_type = IPAddrPair;
+
+  IPAddrPair() = default; ///< Default construct empty pair.
+
+  /** Construct with IPv4 address.
+   *
+   * @param a4 Address.
+   */
+  IPAddrPair(swoc::IP4Addr a4);
+
+  /** Construct with IPv6 address.
+   *
+   * @param a6 Address.
+   */
+  IPAddrPair(swoc::IP6Addr a6);
+
+  /// @return @c true if either address is present.
+  bool has_value() const;
+
+  /// @return @c true if an IPv4 address is present.
+  bool has_ip4() const;
+
+  /// @return @c true if an IPv6 address is present.
+  bool has_ip6() const;
+
+  /// @return The IPv4 address
+  /// @note Does not check if the address is present.
+  swoc::IP4Addr const &ip4() const;
+
+  /// @return The IPv6 address
+  /// @note Does not check if the address is present.
+  swoc::IP6Addr const &ip6() const;
+
+  /** Assign the IPv4 address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP4Addr const &addr);
+
+  /** Assign the IPv6 address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP6Addr const &addr);
+
+  /** Assign an address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   *
+   * The appropriate internal address is assigned based on the address in @a addr.
+   */
+  self_type &operator=(swoc::IPAddr const &addr);
+
+protected:
+  std::optional<swoc::IP4Addr> _ip4;
+  std::optional<swoc::IP6Addr> _ip6;
+};
+
+inline IPAddrPair::IPAddrPair(swoc::IP4Addr a4) : _ip4(a4) {}
+inline IPAddrPair::IPAddrPair(swoc::IP6Addr a6) : _ip6(a6) {}
+
+inline bool
+IPAddrPair::has_value() const
+{
+  return _ip4.has_value() || _ip6.has_value();
+}
+
+inline bool
+IPAddrPair::has_ip4() const
+{
+  return _ip4.has_value();
+}
+
+inline bool
+IPAddrPair::has_ip6() const
+{
+  return _ip6.has_value();
+}
+
+inline swoc::IP4Addr const &
+IPAddrPair::ip4() const
+{
+  return _ip4.value();
+}
+
+inline swoc::IP6Addr const &
+IPAddrPair::ip6() const
+{
+  return _ip6.value();
+}
+
+inline auto
+IPAddrPair::operator=(swoc::IP4Addr const &addr) -> self_type &
+{
+  _ip4 = addr;
+  return *this;
+}
+
+inline auto
+IPAddrPair::operator=(swoc::IP6Addr const &addr) -> self_type &
+{
+  _ip6 = addr;
+  return *this;
+}
+
+inline auto
+IPAddrPair::operator=(swoc::IPAddr const &addr) -> self_type &
+{
+  if (addr.is_ip4()) {
+    _ip4 = addr.ip4();
+  } else if (addr.is_ip6()) {
+    _ip6 = addr.ip6();
+  }
+
+  return *this;
+}
+
+/// Pair of services, each optional.
+/// Used in situations where both IPv4 and IPv6 may be needed.
+class IPSrvPair
+{
+  using self_type = IPSrvPair;
+
+public:
+  IPSrvPair() = default; ///< Default construct empty pair.
+
+  /** Construct from address(es) and port.
+   *
+   * @param a4 IPv4 address.
+   * @param a6 IPv6 address.
+   * @param port Port
+   *
+   * @a port is used for both service instances.
+   */
+  IPSrvPair(swoc::IP4Addr const &a4, swoc::IP6Addr const &a6, in_port_t port = 0);
+
+  /** Construct from IPv4 address and optional port.
+   *
+   * @param a4 IPv4 address
+   * @param port Port.
+   */
+  IPSrvPair(swoc::IP4Addr const &a4, in_port_t port = 0);
+
+  /** Construct from IPv6 address and optional port.
+   *
+   * @param a6 IPv6 address
+   * @param port Port.
+   */
+  IPSrvPair(swoc::IP6Addr const &a6, in_port_t port = 0);
+
+  /** Construct from an address pair and optional port.
+   *
+   * @param a Address pair.
+   * @param port port.
+   *
+   * For each family the service is instantatied only if the address is present in @a a.
+   * @a port is used for all service instances.
+   */
+  explicit IPSrvPair(IPAddrPair const &a, in_port_t port = 0);
+
+  /// @return @c true if any service is present.
+  bool has_value() const;
+
+  /// @return @c true if the IPv4 service is present.
+  bool has_ip4() const;
+
+  /// @return @c true if the the IPv6 service is present.
+  bool has_ip6() const;
+
+  /// @return The IPv4 service.
+  /// @note Does not check if the service is present.
+  swoc::IP4Srv const &ip4() const;
+
+  /// @return The IPv6 service.
+  /// @note Does not check if the service is present.
+  swoc::IP6Srv const &ip6() const;
+
+  /** Assign the IPv4 service.
+   *
+   * @param srv Service to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP4Srv const &srv);
+
+  /** Assign the IPv6 service.
+   *
+   * @param srv Service to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP6Srv const &srv);
+
+  /** Assign a service.
+   *
+   * @param srv Service to assign.
+   * @return @a this
+   *
+   * The assigned service is the same family as @a srv.
+   */
+  self_type &operator=(swoc::IPSrv const &srv);
+
+protected:
+  std::optional<swoc::IP4Srv> _ip4;
+  std::optional<swoc::IP6Srv> _ip6;
+};
+
+inline IPSrvPair::IPSrvPair(swoc::IP4Addr const &a4, swoc::IP6Addr const &a6, in_port_t port)
+  : _ip4(swoc::IP4Srv(a4, port)), _ip6(swoc::IP6Srv(a6, port))
+{
+}
+
+inline IPSrvPair::IPSrvPair(swoc::IP4Addr const &a4, in_port_t port) : _ip4(swoc::IP4Srv(a4, port)) {}
+
+inline IPSrvPair::IPSrvPair(swoc::IP6Addr const &a6, in_port_t port) : _ip6(swoc::IP6Srv(a6, port)) {}
+
+inline IPSrvPair::IPSrvPair(IPAddrPair const &a, in_port_t port)
+{
+  if (a.has_ip4()) {
+    _ip4 = swoc::IP4Srv(a.ip4(), port);
+  }
+
+  if (a.has_ip6()) {
+    _ip6 = swoc::IP6Srv(a.ip6(), port);
+  }
+}
+
+inline bool
+IPSrvPair::has_value() const
+{
+  return _ip4.has_value() || _ip6.has_value();
+}
+
+inline bool
+IPSrvPair::has_ip4() const
+{
+  return _ip4.has_value();
+}
+
+inline bool
+IPSrvPair::has_ip6() const
+{
+  return _ip6.has_value();
+}
+
+inline swoc::IP4Srv const &
+IPSrvPair::ip4() const
+{
+  return _ip4.value();
+}
+
+inline swoc::IP6Srv const &
+IPSrvPair::ip6() const
+{
+  return _ip6.value();
+}
+
+inline auto
+IPSrvPair::operator=(swoc::IP4Srv const &srv) -> self_type &
+{
+  _ip4 = srv;
+  return *this;
+}
+
+inline auto
+IPSrvPair::operator=(swoc::IP6Srv const &srv) -> self_type &
+{
+  _ip6 = srv;
+  return *this;
+}
+
+inline auto
+IPSrvPair::operator=(swoc::IPSrv const &srv) -> self_type &
+{
+  if (srv.is_ip4()) {
+    _ip4 = srv.ip4();
+  } else if (srv.is_ip6()) {
+    _ip6 = srv.ip6();
+  }
+  return *this;
+}
+
+/** Get the best address info for @a name.
+
+ * @param name Address / host.
+ * @return An address pair.
+ *
+ * If @a name is a valid IP address it is interpreted as such. Otherwise it is presumed
+ * to be a host name suitable for resolution using @c getaddrinfo. The "best" address is
+ * selected by ranking the types of addresses in the order
+ *
+ * - Global, multi-cast, non-routable (private), link local, loopback
+ *
+ * For a host name, an IPv4 and IPv6 address may be returned. The "best" is computed independently
+ * for each family.
+ *
+ * @see getaddrinfo
+ * @see ts::getbestsrvinfo
+ */
+IPAddrPair getbestaddrinfo(swoc::TextView name);
+
+/** Get the best address and port info for @a name.
+
+ * @param name Address / host.
+ * @return An address pair.
+ *
+ * If @a name is a valid IP address (with optional port) it is interpreted as such. Otherwise it is
+ * presumed to be a host name (with optional port) suitable for resolution using @c getaddrinfo. The "best" address is
+ * selected by ranking the types of addresses in the order
+ *
+ * - Global, multi-cast, non-routable (private), link local, loopback
+ *
+ * For a host name, an IPv4 and IPv6 service may be returned. The "best" is computed independently
+ * for each family. The port, if present, is the same for all returned services.
+ *
+ * @see getaddrinfo
+ * @see ts::getbestaddrinfo
+ */
+IPSrvPair getbestsrvinfo(swoc::TextView name);
+
+/** An IPSpace that contains only addresses.
+ *
+ * This is to @c IPSpace as @c std::set is to @c std::map. The @c value_type is removed from the API
+ * and only the keys are visible. This suits use cases where the goal is to track the presence of
+ * addresses without any additional data.
+ *
+ * @note Because there is only one value stored, there is no difference between @c mark and @c fill.
+ */
+class IPAddrSet
+{
+  using self_type = IPAddrSet;
+
+public:
+  /// Default construct empty set.
+  IPAddrSet() = default;
+
+  /** Add addresses to the set.
+   *
+   * @param r Range of addresses to add.
+   * @return @a this
+   *
+   * Identical to @c fill.
+   */
+  self_type &mark(swoc::IPRange const &r);
+
+  /** Add addresses to the set.
+   *
+   * @param r Range of addresses to add.
+   * @return @a this
+   *
+   * Identical to @c mark.
+   */
+  self_type &fill(swoc::IPRange const &r);
+
+  /// @return @c true if @a addr is in the set.
+  bool contains(swoc::IPAddr const &addr) const;
+
+  /// @return Number of ranges in the set.
+  size_t count() const;
+
+  /// Remove all addresses in the set.
+  void clear();
+
+protected:
+  /// Empty struct to use for payload.
+  /// This declares the struct and defines the singleton instance used.
+  static inline constexpr struct Mark {
+    using self_type = Mark;
+    /// @internal @c IPSpace requires equality / inequality operators.
+    /// These make all instance equal to each other.
+    bool operator==(self_type const &that);
+    bool operator!=(self_type const &that);
+  } MARK{};
+
+  /// The address set.
+  swoc::IPSpace<Mark> _addrs;
+};
+
+inline auto
+IPAddrSet::mark(swoc::IPRange const &r) -> self_type &
+{
+  _addrs.mark(r, MARK);
+  return *this;
+}
+
+inline auto
+IPAddrSet::fill(swoc::IPRange const &r) -> self_type &
+{
+  _addrs.mark(r, MARK);
+  return *this;
+}
+
+inline bool
+IPAddrSet::contains(swoc::IPAddr const &addr) const
+{
+  return _addrs.find(addr) != _addrs.end();
+}
+
+inline size_t
+IPAddrSet::count() const
+{
+  return _addrs.count();
+}
+
+inline void
+IPAddrSet::clear()
+{
+  _addrs.clear();
+}
+
+inline bool
+IPAddrSet::Mark::operator==(IPAddrSet::Mark::self_type const &that)
+{
+  return true;
+}
+
+inline bool
+IPAddrSet::Mark::operator!=(IPAddrSet::Mark::self_type const &that)
+{
+  return false;
+}
+
+} // namespace ts
diff --git a/include/tscpp/util/ts_meta.h b/include/tscpp/util/ts_meta.h
index cd9ada3..4d150d9 100644
--- a/include/tscpp/util/ts_meta.h
+++ b/include/tscpp/util/ts_meta.h
@@ -94,5 +94,12 @@
   CaseVoidFunc()
   {
   }
+
+  // helper type for the visitor
+  template <class... Ts> struct overloaded : Ts... {
+    using Ts::operator()...;
+  };
+  // explicit deduction guide (not needed as of C++20)
+  template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
 } // namespace meta
 } // namespace ts
diff --git a/iocore/aio/Makefile.am b/iocore/aio/Makefile.am
index b565c55..41fc5ad 100644
--- a/iocore/aio/Makefile.am
+++ b/iocore/aio/Makefile.am
@@ -20,6 +20,7 @@
 	-I$(abs_top_srcdir)/iocore/eventsystem \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
+	@SWOC_INCLUDES@
 	$(TS_INCLUDES)
 
 TESTS = test_AIO.sample
@@ -35,7 +36,7 @@
 
 test_AIO_LDFLAGS = \
 	@AM_LDFLAGS@ \
-	@OPENSSL_LDFLAGS@
+	@OPENSSL_LDFLAGS@ @SWOC_LDFLAGS@
 
 test_AIO_SOURCES = \
 	test_AIO.cc
@@ -49,7 +50,7 @@
 	-I$(abs_top_srcdir)/proxy/logging \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
-	@OPENSSL_INCLUDES@
+	@SWOC_INCLUDES@ @OPENSSL_INCLUDES@
 
 test_AIO_LDADD = \
 	libinkaio.a \
@@ -59,7 +60,7 @@
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@
+	@SWOC_LIBS@ @HWLOC_LIBS@
 
 include $(top_srcdir)/build/tidy.mk
 
diff --git a/iocore/cache/Cache.cc b/iocore/cache/Cache.cc
index fcb1438..cb64b6e 100644
--- a/iocore/cache/Cache.cc
+++ b/iocore/cache/Cache.cc
@@ -1998,12 +1998,10 @@
       if (!DISK_BAD(d)) {
         snprintf(message, sizeof(message), "Error accessing Disk %s [%d/%d]", d->path, d->num_errors, cache_config_max_disk_errors);
         Warning("%s", message);
-        RecSignalManager(REC_SIGNAL_CACHE_WARNING, message);
       } else if (!DISK_BAD_SIGNALLED(d)) {
         snprintf(message, sizeof(message), "too many errors accessing disk %s [%d/%d]: declaring disk bad", d->path, d->num_errors,
                  cache_config_max_disk_errors);
         Warning("%s", message);
-        RecSignalManager(REC_SIGNAL_CACHE_ERROR, message);
         cacheProcessor.mark_storage_offline(d); // take it out of service
       }
       break;
diff --git a/iocore/cache/CacheHosting.cc b/iocore/cache/CacheHosting.cc
index 78f6018..74d6a61 100644
--- a/iocore/cache/CacheHosting.cc
+++ b/iocore/cache/CacheHosting.cc
@@ -286,8 +286,7 @@
       errPtr  = parseConfigLine(const_cast<char *>(tmp), current, &config_tags);
 
       if (errPtr != nullptr) {
-        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d : %s", matcher_name, config_file_path,
-                         line_num, errPtr);
+        Warning("%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, errPtr);
         ats_free(current);
       } else {
         // Line parsed ok.  Figure out what the destination
@@ -366,8 +365,7 @@
         hostMatch->NewEntry(current);
       }
     } else {
-      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry with unknown type at line %d", matcher_name,
-                       config_file_path, current->line_num);
+      Warning("%s discarding %s entry with unknown type at line %d", matcher_name, config_file_path, current->line_num);
     }
 
     // Deallocate the parsing structure
@@ -380,9 +378,7 @@
 
   if (!generic_rec_initd) {
     const char *cache_type = (type == CACHE_HTTP_TYPE) ? "http" : "mixt";
-    RecSignalWarning(REC_SIGNAL_CONFIG_ERROR,
-                     "No Volumes specified for Generic Hostnames for %s documents: %s cache will be disabled", cache_type,
-                     cache_type);
+    Warning("No Volumes specified for Generic Hostnames for %s documents: %s cache will be disabled", cache_type, cache_type);
   }
 
   ink_assert(second_pass == numEntries);
@@ -436,7 +432,7 @@
     }
   }
   if (!num_cachevols) {
-    RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "error: No volumes found for Cache Type %d", type);
+    Warning("error: No volumes found for Cache Type %d", type);
     return -1;
   }
   vols        = static_cast<Vol **>(ats_malloc(num_vols * sizeof(Vol *)));
@@ -485,16 +481,18 @@
           s++;
           if (!(*s)) {
             const char *errptr = "A volume number expected";
-            RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d :%s", "[CacheHosting]", config_file,
-                             line_info->line_num, errptr);
-            ats_free(val);
+            Warning("%s discarding %s entry at line %d :%s", "[CacheHosting]", config_file, line_info->line_num, errptr);
+            if (val != nullptr) {
+              ats_free(val);
+            }
             return -1;
           }
         }
         if ((*s < '0') || (*s > '9')) {
-          RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d : bad token [%c]", "[CacheHosting]",
-                           config_file, line_info->line_num, *s);
-          ats_free(val);
+          Warning("%s discarding %s entry at line %d : bad token [%c]", "[CacheHosting]", config_file, line_info->line_num, *s);
+          if (val != nullptr) {
+            ats_free(val);
+          }
           return -1;
         }
         s++;
@@ -528,9 +526,11 @@
             }
           }
           if (!is_vol_present) {
-            RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d : bad volume number [%d]",
-                             "[CacheHosting]", config_file, line_info->line_num, volume_number);
-            ats_free(val);
+            Warning("%s discarding %s entry at line %d : bad volume number [%d]", "[CacheHosting]", config_file,
+                    line_info->line_num, volume_number);
+            if (val != nullptr) {
+              ats_free(val);
+            }
             return -1;
           }
           if (c == '\0') {
@@ -545,14 +545,12 @@
       break;
     }
 
-    RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d : bad token [%s]", "[CacheHosting]", config_file,
-                     line_info->line_num, label);
+    Warning("%s discarding %s entry at line %d : bad token [%s]", "[CacheHosting]", config_file, line_info->line_num, label);
     return -1;
   }
 
   if (i == MATCHER_MAX_TOKENS) {
-    RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d : No volumes specified", "[CacheHosting]",
-                     config_file, line_info->line_num);
+    Warning("%s discarding %s entry at line %d : No volumes specified", "[CacheHosting]", config_file, line_info->line_num);
     return -1;
   }
 
@@ -758,8 +756,7 @@
     }
 
     if (err) {
-      RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num,
-                       err);
+      Warning("%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, err);
     } else if (volume_number && size && scheme) {
       /* add the config */
 
diff --git a/iocore/cache/Makefile.am b/iocore/cache/Makefile.am
index 3dec1d8..b6953f1 100644
--- a/iocore/cache/Makefile.am
+++ b/iocore/cache/Makefile.am
@@ -27,6 +27,7 @@
 	-I$(abs_top_srcdir)/proxy/http/remap \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
+	@SWOC_INCLUDES@
 	$(TS_INCLUDES)
 
 noinst_LIBRARIES = libinkcache.a
@@ -83,6 +84,7 @@
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
 	-I$(abs_top_srcdir)/tests/include \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES) \
 	@OPENSSL_INCLUDES@
 
@@ -106,6 +108,7 @@
 	$(top_builddir)/src/records/librecords_p.a \
 	$(top_builddir)/lib/fastlz/libfastlz.a \
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	@SWOC_LIBS@ \
 	@HWLOC_LIBS@ \
 	@LIBPCRE@ \
 	@LIBRESOLV@ \
diff --git a/iocore/cache/Store.cc b/iocore/cache/Store.cc
index 00f3dfd..5c7c799 100644
--- a/iocore/cache/Store.cc
+++ b/iocore/cache/Store.cc
@@ -381,7 +381,6 @@
     Debug("cache_init", "Store::read_config - ns = new Span; ns->init(\"%s\",%" PRId64 "), forced volume=%d%s%s", pp.c_str(), size,
           volume_num, seed ? " id=" : "", seed ? seed : "");
     if ((err = ns->init(pp.c_str(), size))) {
-      RecSignalWarning(REC_SIGNAL_SYSTEM_ERROR, "could not initialize storage \"%s\" [%s]", pp.c_str(), err);
       Debug("cache_init", "Store::read_config - could not initialize storage \"%s\" [%s]", pp.c_str(), err);
       delete ns;
       continue;
diff --git a/iocore/cache/test/stub.cc b/iocore/cache/test/stub.cc
index b247da1..78ffe2f 100644
--- a/iocore/cache/test/stub.cc
+++ b/iocore/cache/test/stub.cc
@@ -179,11 +179,6 @@
   return nullptr;
 }
 
-void
-HostStatus::createHostStat(const std::string_view name, const char *data)
-{
-}
-
 HostStatus::HostStatus() {}
 
 HostStatus::~HostStatus() {}
diff --git a/iocore/dns/Makefile.am b/iocore/dns/Makefile.am
index a7287d2..13231ac 100644
--- a/iocore/dns/Makefile.am
+++ b/iocore/dns/Makefile.am
@@ -25,6 +25,7 @@
 	-I$(abs_top_srcdir)/proxy/hdrs \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES)
 
 noinst_LIBRARIES = libinkdns.a
diff --git a/iocore/eventsystem/Makefile.am b/iocore/eventsystem/Makefile.am
index 97ee20b..15265d5 100644
--- a/iocore/eventsystem/Makefile.am
+++ b/iocore/eventsystem/Makefile.am
@@ -19,7 +19,7 @@
 AM_CPPFLAGS += \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
-	$(TS_INCLUDES)
+	$(TS_INCLUDES) @SWOC_INCLUDES@
 
 TESTS = $(check_PROGRAMS)
 
@@ -77,7 +77,7 @@
 
 test_LD_FLAGS = \
 	@AM_LDFLAGS@ \
-	@OPENSSL_LDFLAGS@
+	@OPENSSL_LDFLAGS@ @SWOC_LDFLAGS@
 
 test_CPP_FLAGS = \
 	$(AM_CPPFLAGS) \
@@ -89,7 +89,7 @@
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
 	-I$(abs_top_srcdir)/tests/include \
-	@OPENSSL_INCLUDES@
+	@OPENSSL_INCLUDES@ @SWOC_INCLUDES@
 
 test_LD_ADD = \
 	libinkevent.a \
@@ -99,7 +99,7 @@
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@
+	@HWLOC_LIBS@ @SWOC_LIBS@
 
 test_EventSystem_SOURCES = unit_tests/test_EventSystem.cc
 test_EventSystem_CPPFLAGS = $(test_CPP_FLAGS)
diff --git a/iocore/eventsystem/P_UnixEThread.h b/iocore/eventsystem/P_UnixEThread.h
index 21ca052..b182463 100644
--- a/iocore/eventsystem/P_UnixEThread.h
+++ b/iocore/eventsystem/P_UnixEThread.h
@@ -32,6 +32,7 @@
 
 #include "I_EThread.h"
 #include "I_EventProcessor.h"
+#include "tscore/ink_atomic.h"
 #include <execinfo.h>
 
 const ink_hrtime DELAY_FOR_RETRY = HRTIME_MSECONDS(10);
diff --git a/iocore/eventsystem/unit_tests/test_EventSystem.cc b/iocore/eventsystem/unit_tests/test_EventSystem.cc
index 5de42b0..776724f 100644
--- a/iocore/eventsystem/unit_tests/test_EventSystem.cc
+++ b/iocore/eventsystem/unit_tests/test_EventSystem.cc
@@ -25,6 +25,7 @@
 #include "catch.hpp"
 
 #include "I_EventSystem.h"
+#include "tscore/ink_atomic.h"
 #include "tscore/I_Layout.h"
 #include "tscore/TSSystemState.h"
 
diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc
index fe64b9b..fadbf9b 100644
--- a/iocore/hostdb/HostDB.cc
+++ b/iocore/hostdb/HostDB.cc
@@ -29,6 +29,7 @@
 #include "tscore/ts_file.h"
 #include "tscore/ink_apidefs.h"
 #include "tscore/bwf_std_format.h"
+#include "MgmtDefs.h" // MgmtInt, MgmtFloat, etc
 #include "HostFile.h"
 
 #include <utility>
@@ -41,6 +42,8 @@
 
 using ts::TextView;
 using std::chrono::duration_cast;
+using swoc::round_down;
+using swoc::round_up;
 
 HostDBProcessor hostDBProcessor;
 int HostDBProcessor::hostdb_strict_round_robin = 0;
@@ -2091,9 +2094,9 @@
 HostDBRecord *
 HostDBRecord::alloc(TextView query_name, unsigned int rr_count, size_t srv_name_size)
 {
-  const ts::Scalar<8> qn_size = ts::round_up(query_name.size() + 1);
-  const ts::Scalar<8> r_size  = ts::round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size);
-  int iobuffer_index          = iobuffer_size_to_index(r_size, hostdb_max_iobuf_index);
+  const swoc::Scalar<8, ssize_t> qn_size = round_up(query_name.size() + 1);
+  const swoc::Scalar<8, ssize_t> r_size  = round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size);
+  int iobuffer_index                     = iobuffer_size_to_index(r_size, hostdb_max_iobuf_index);
   ink_release_assert(iobuffer_index >= 0);
   auto ptr = ioBufAllocator[iobuffer_index].alloc_void();
   memset(ptr, 0, r_size);
diff --git a/iocore/hostdb/I_HostDBProcessor.h b/iocore/hostdb/I_HostDBProcessor.h
index 723f62f..5839799 100644
--- a/iocore/hostdb/I_HostDBProcessor.h
+++ b/iocore/hostdb/I_HostDBProcessor.h
@@ -33,6 +33,9 @@
 #include "tscore/ink_inet.h"
 #include "tscore/ink_resolver.h"
 #include "tscore/HTTPVersion.h"
+
+#include "swoc/MemSpan.h"
+
 #include "I_EventSystem.h"
 #include "SRV.h"
 
@@ -389,7 +392,7 @@
   ts::TextView name_view() const;
 
   /// Get the array of info instances.
-  ts::MemSpan<HostDBInfo> rr_info();
+  swoc::MemSpan<HostDBInfo> rr_info();
 
   /** Find a host record by IP address.
    *
@@ -790,7 +793,7 @@
   ip_timestamp = hostdb_current_timestamp;
 }
 
-inline ts::MemSpan<HostDBInfo>
+inline swoc::MemSpan<HostDBInfo>
 HostDBRecord::rr_info()
 {
   return {this->apply_offset<HostDBInfo>(rr_offset), rr_count};
diff --git a/iocore/hostdb/Makefile.am b/iocore/hostdb/Makefile.am
index 681c098..1dd35ca 100644
--- a/iocore/hostdb/Makefile.am
+++ b/iocore/hostdb/Makefile.am
@@ -25,6 +25,7 @@
 	-I$(abs_top_srcdir)/proxy/http \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES)
 
 EXTRA_DIST = I_HostDB.h
@@ -57,7 +58,7 @@
 
 test_LD_FLAGS = \
 	@AM_LDFLAGS@ \
-	@OPENSSL_LDFLAGS@
+	@SWOC_LDFLAGS@ @OPENSSL_LDFLAGS@
 
 test_CPP_FLAGS = \
 	$(AM_CPPFLAGS) \
@@ -77,7 +78,7 @@
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@
+	@SWOC_LIBS@ @HWLOC_LIBS@
 
 test_RefCountCache_CPPFLAGS = $(test_CPP_FLAGS)
 
diff --git a/iocore/hostdb/test_HostFile.cc b/iocore/hostdb/test_HostFile.cc
index 4e1d617..ee002e8 100644
--- a/iocore/hostdb/test_HostFile.cc
+++ b/iocore/hostdb/test_HostFile.cc
@@ -127,14 +127,15 @@
 // NOTE(cmcfarlen): need this destructor defined so we don't have to link in the entire project for this test
 HostDBHash::~HostDBHash() {}
 
-#include "tscore/Scalar.h"
+#include "swoc/Scalar.h"
 
 HostDBRecord *
 HostDBRecord::alloc(ts::TextView query_name, unsigned int rr_count, size_t srv_name_size)
 {
-  const ts::Scalar<8> qn_size = ts::round_up(query_name.size() + 1);
-  const ts::Scalar<8> r_size  = ts::round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size);
-  auto ptr                    = malloc(r_size);
+  const swoc::Scalar<8, ssize_t> qn_size = swoc::round_up(query_name.size() + 1);
+  const swoc::Scalar<8, ssize_t> r_size =
+    swoc::round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size);
+  auto ptr = malloc(r_size);
   memset(ptr, 0, r_size);
   auto self = static_cast<self_type *>(ptr);
   new (self) self_type();
diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h
index 8956927..1389beb 100644
--- a/iocore/net/I_NetVConnection.h
+++ b/iocore/net/I_NetVConnection.h
@@ -229,6 +229,9 @@
 
   bool tls_upstream = false;
 
+  unsigned char alpn_protocols_array[MAX_ALPN_STRING];
+  int alpn_protocols_array_size = 0;
+
   /**
    * Set to DISABLED, PERFMISSIVE, or ENFORCED
    * Controls how the server certificate verification is handled
diff --git a/iocore/net/I_UDPConnection.h b/iocore/net/I_UDPConnection.h
index bc07a83..a56f5cd 100644
--- a/iocore/net/I_UDPConnection.h
+++ b/iocore/net/I_UDPConnection.h
@@ -97,7 +97,7 @@
      bindToThread() automatically so that the sockets can be passed to
      other Continuations.
   */
-  void bindToThread(Continuation *c);
+  void bindToThread(Continuation *c, EThread *t);
 
   virtual void UDPConnection_is_abstract() = 0;
 };
diff --git a/iocore/net/I_UDPNet.h b/iocore/net/I_UDPNet.h
index 0cc608c..a08d01a 100644
--- a/iocore/net/I_UDPNet.h
+++ b/iocore/net/I_UDPNet.h
@@ -44,6 +44,7 @@
 class UDPNetProcessor : public Processor
 {
 public:
+  virtual EventType register_event_type()                 = 0;
   int start(int n_upd_threads, size_t stacksize) override = 0;
 
   // this function was internal initially.. this is required for public and
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index f6f34dc..dfe19ff 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -33,7 +33,8 @@
 	$(TS_INCLUDES) \
 	@OPENSSL_INCLUDES@ \
 	@BORINGOCSP_INCLUDES@ \
-	@YAMLCPP_INCLUDES@
+	@YAMLCPP_INCLUDES@ \
+	@SWOC_INCLUDES@
 
 TESTS = $(check_PROGRAMS)
 
@@ -43,7 +44,8 @@
 test_certlookup_LDFLAGS = \
 	@AM_LDFLAGS@ \
 	@OPENSSL_LDFLAGS@ \
-	@YAMLCPP_LDFLAGS@
+	@YAMLCPP_LDFLAGS@ \
+	@SWOC_LDFLAGS@
 
 test_certlookup_SOURCES = \
 	test_certlookup.cc \
@@ -54,7 +56,8 @@
 	$(top_builddir)/src/tscore/libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
 	$(top_builddir)/proxy/ParentSelectionStrategy.o \
-	@YAMLCPP_LIBS@
+	@YAMLCPP_LIBS@ \
+	@SWOC_LIBS@
 
 test_UDPNet_CPPFLAGS = \
 	$(AM_CPPFLAGS) \
@@ -64,12 +67,14 @@
 	-I$(abs_top_srcdir)/proxy/http \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
-	@OPENSSL_INCLUDES@
+	@OPENSSL_INCLUDES@ \
+	@SWOC_INCLUDES@
 
 test_UDPNet_LDFLAGS = \
 	@AM_LDFLAGS@ \
 	@OPENSSL_LDFLAGS@ \
-	@YAMLCPP_LDFLAGS@
+	@YAMLCPP_LDFLAGS@ \
+	@SWOC_LDFLAGS@
 
 test_UDPNet_LDADD = \
 	libinknet.a \
@@ -79,7 +84,7 @@
 	$(top_builddir)/src/tscore/libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/proxy/hdrs/libhdrs.a \
 	$(top_builddir)/proxy/ParentSelectionStrategy.o \
-	@HWLOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@
+	@HWLOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@ @SWOC_LIBS@
 
 test_UDPNet_SOURCES = \
 	libinknet_stub.cc \
@@ -103,7 +108,8 @@
 test_libinknet_LDFLAGS = \
 	@AM_LDFLAGS@ \
 	@OPENSSL_LDFLAGS@ \
-	@YAMLCPP_LDFLAGS@
+	@YAMLCPP_LDFLAGS@ \
+	@SWOC_LDFLAGS@
 
 test_libinknet_LDADD = \
 	libinknet.a \
@@ -114,7 +120,7 @@
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/proxy/ParentSelectionStrategy.o \
-	@HWLOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@
+	@HWLOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@ @SWOC_LIBS@
 
 libinknet_a_SOURCES = \
 	ALPNSupport.cc \
@@ -208,6 +214,21 @@
 	SSLDynlock.cc
 
 if ENABLE_QUIC
+if USE_QUICHE
+libinknet_a_SOURCES += \
+  P_QUICClosedConCollector.h \
+  P_QUICPacketHandler.h \
+  P_QUICNetProcessor.h \
+  P_QUICNetVConnection.h \
+  P_QUICNextProtocolAccept.h \
+  QUICClosedConCollector.cc \
+  QUICMultiCertConfigLoader.cc \
+  QUICNet.cc \
+  QUICNetProcessor_quiche.cc \
+  QUICNetVConnection_quiche.cc \
+  QUICNextProtocolAccept_quiche.cc \
+  QUICPacketHandler_quiche.cc
+else
 libinknet_a_SOURCES += \
   P_QUICClosedConCollector.h \
   P_QUICPacketHandler.h \
@@ -223,6 +244,7 @@
   QUICNetVConnection.cc \
   QUICNextProtocolAccept.cc
 endif
+endif
 
 if BUILD_TESTS
 libinknet_a_SOURCES += \
diff --git a/iocore/net/Net.cc b/iocore/net/Net.cc
index f0cac5b..7f1ec3c 100644
--- a/iocore/net/Net.cc
+++ b/iocore/net/Net.cc
@@ -84,11 +84,9 @@
     {"proxy.process.net.calls_to_read", net_calls_to_read_stat},
     {"proxy.process.net.calls_to_read_nodata", net_calls_to_read_nodata_stat},
     {"proxy.process.net.calls_to_readfromnet", net_calls_to_readfromnet_stat},
-    {"proxy.process.net.calls_to_readfromnet_afterpoll", net_calls_to_readfromnet_afterpoll_stat},
     {"proxy.process.net.calls_to_write", net_calls_to_write_stat},
     {"proxy.process.net.calls_to_write_nodata", net_calls_to_write_nodata_stat},
     {"proxy.process.net.calls_to_writetonet", net_calls_to_writetonet_stat},
-    {"proxy.process.net.calls_to_writetonet_afterpoll", net_calls_to_writetonet_afterpoll_stat},
     {"proxy.process.net.inactivity_cop_lock_acquire_failure", inactivity_cop_lock_acquire_failure_stat},
     {"proxy.process.net.net_handler_run", net_handler_run_stat},
     {"proxy.process.net.read_bytes", net_read_bytes_stat},
@@ -121,11 +119,9 @@
   NET_CLEAR_DYN_STAT(net_connections_currently_open_stat);
   NET_CLEAR_DYN_STAT(net_accepts_currently_open_stat);
   NET_CLEAR_DYN_STAT(net_calls_to_readfromnet_stat);
-  NET_CLEAR_DYN_STAT(net_calls_to_readfromnet_afterpoll_stat);
   NET_CLEAR_DYN_STAT(net_calls_to_read_stat);
   NET_CLEAR_DYN_STAT(net_calls_to_read_nodata_stat);
   NET_CLEAR_DYN_STAT(net_calls_to_writetonet_stat);
-  NET_CLEAR_DYN_STAT(net_calls_to_writetonet_afterpoll_stat);
   NET_CLEAR_DYN_STAT(net_calls_to_write_stat);
   NET_CLEAR_DYN_STAT(net_calls_to_write_nodata_stat);
   NET_CLEAR_DYN_STAT(socks_connections_currently_open_stat);
diff --git a/iocore/net/P_Net.h b/iocore/net/P_Net.h
index 6ac9bdd..9381b0a 100644
--- a/iocore/net/P_Net.h
+++ b/iocore/net/P_Net.h
@@ -38,11 +38,9 @@
   net_connections_currently_open_stat,
   net_accepts_currently_open_stat,
   net_calls_to_readfromnet_stat,
-  net_calls_to_readfromnet_afterpoll_stat,
   net_calls_to_read_stat,
   net_calls_to_read_nodata_stat,
   net_calls_to_writetonet_stat,
-  net_calls_to_writetonet_afterpoll_stat,
   net_calls_to_write_stat,
   net_calls_to_write_nodata_stat,
   socks_connections_successful_stat,
diff --git a/iocore/net/P_QUICNet.h b/iocore/net/P_QUICNet.h
index 09468bc..4e452bb 100644
--- a/iocore/net/P_QUICNet.h
+++ b/iocore/net/P_QUICNet.h
@@ -65,8 +65,12 @@
   Que(UDPPacketInternal, link) _longInQueue;
 
 private:
+#if HAVE_QUICHE_H
+  void _process_packet(QUICPollEvent *e, NetHandler *nh);
+#else
   void _process_short_header_packet(QUICPollEvent *e, NetHandler *nh);
   void _process_long_header_packet(QUICPollEvent *e, NetHandler *nh);
+#endif
 };
 
 static inline QUICPollCont *
diff --git a/iocore/net/P_QUICNetProcessor.h b/iocore/net/P_QUICNetProcessor.h
index 68e7228..9a4d6c9 100644
--- a/iocore/net/P_QUICNetProcessor.h
+++ b/iocore/net/P_QUICNetProcessor.h
@@ -18,63 +18,15 @@
   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.
  */
 
-/****************************************************************************
-
-  P_QUICNetProcessor.h
-
-  The QUIC version of the UnixNetProcessor class.  The majority of the logic
-  is in UnixNetProcessor.  The QUICNetProcessor provides the following:
-
-  * QUIC library initialization through the start() method.
-  * Allocation of a QUICNetVConnection through the allocate_vc virtual method.
-
-  Possibly another pass through could simplify the allocate_vc logic too, but
-  I think I will stop here for now.
-
- ****************************************************************************/
 #pragma once
 
-#include "tscore/ink_platform.h"
-#include "P_Net.h"
-#include "quic/QUICConnectionTable.h"
+#include "tscore/ink_config.h"
 
-class UnixNetVConnection;
-class QUICResetTokenTable;
-struct NetAccept;
-
-//////////////////////////////////////////////////////////////////
-//
-//  class QUICNetProcessor
-//
-//////////////////////////////////////////////////////////////////
-class QUICNetProcessor : public UnixNetProcessor
-{
-public:
-  QUICNetProcessor();
-  virtual ~QUICNetProcessor();
-
-  void init() override;
-  virtual int start(int, size_t stacksize) override;
-  // TODO: refactoring NetProcessor::connect_re and UnixNetProcessor::connect_re_internal
-  // Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts) override;
-  Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts);
-
-  virtual NetAccept *createNetAccept(const NetProcessor::AcceptOptions &opt) override;
-  virtual NetVConnection *allocate_vc(EThread *t) override;
-
-  Action *main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt) override;
-
-  off_t quicPollCont_offset;
-
-private:
-  QUICNetProcessor(const QUICNetProcessor &);
-  QUICNetProcessor &operator=(const QUICNetProcessor &);
-
-  QUICConnectionTable *_ctable = nullptr;
-  QUICResetTokenTable *_rtable = nullptr;
-};
-
-extern QUICNetProcessor quic_NetProcessor;
+#if HAVE_QUICHE_H
+#include "P_QUICNetProcessor_quiche.h"
+#else
+#include "P_QUICNetProcessor_native.h"
+#endif
diff --git a/iocore/net/P_QUICNetProcessor_native.h b/iocore/net/P_QUICNetProcessor_native.h
new file mode 100644
index 0000000..68e7228
--- /dev/null
+++ b/iocore/net/P_QUICNetProcessor_native.h
@@ -0,0 +1,80 @@
+/** @file
+
+  A brief file description
+
+  @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
+
+ */
+
+/****************************************************************************
+
+  P_QUICNetProcessor.h
+
+  The QUIC version of the UnixNetProcessor class.  The majority of the logic
+  is in UnixNetProcessor.  The QUICNetProcessor provides the following:
+
+  * QUIC library initialization through the start() method.
+  * Allocation of a QUICNetVConnection through the allocate_vc virtual method.
+
+  Possibly another pass through could simplify the allocate_vc logic too, but
+  I think I will stop here for now.
+
+ ****************************************************************************/
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Net.h"
+#include "quic/QUICConnectionTable.h"
+
+class UnixNetVConnection;
+class QUICResetTokenTable;
+struct NetAccept;
+
+//////////////////////////////////////////////////////////////////
+//
+//  class QUICNetProcessor
+//
+//////////////////////////////////////////////////////////////////
+class QUICNetProcessor : public UnixNetProcessor
+{
+public:
+  QUICNetProcessor();
+  virtual ~QUICNetProcessor();
+
+  void init() override;
+  virtual int start(int, size_t stacksize) override;
+  // TODO: refactoring NetProcessor::connect_re and UnixNetProcessor::connect_re_internal
+  // Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts) override;
+  Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts);
+
+  virtual NetAccept *createNetAccept(const NetProcessor::AcceptOptions &opt) override;
+  virtual NetVConnection *allocate_vc(EThread *t) override;
+
+  Action *main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt) override;
+
+  off_t quicPollCont_offset;
+
+private:
+  QUICNetProcessor(const QUICNetProcessor &);
+  QUICNetProcessor &operator=(const QUICNetProcessor &);
+
+  QUICConnectionTable *_ctable = nullptr;
+  QUICResetTokenTable *_rtable = nullptr;
+};
+
+extern QUICNetProcessor quic_NetProcessor;
diff --git a/iocore/net/P_QUICNetProcessor_quiche.h b/iocore/net/P_QUICNetProcessor_quiche.h
new file mode 100644
index 0000000..2da41c2
--- /dev/null
+++ b/iocore/net/P_QUICNetProcessor_quiche.h
@@ -0,0 +1,80 @@
+/** @file
+
+  A brief file description
+
+  @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
+
+ */
+
+/****************************************************************************
+
+  QUICNetProcessor.h
+
+  The Quiche version of the UnixNetProcessor class.  The majority of the logic
+  is in UnixNetProcessor.  QUICNetProcessor provides the following:
+
+  * QUIC library initialization through the start() method.
+  * Allocation of a QUICNetVConnection through the allocate_vc virtual method.
+
+  Possibly another pass through could simplify the allocate_vc logic too, but
+  I think I will stop here for now.
+
+ ****************************************************************************/
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Net.h"
+#include "quic/QUICConnectionTable.h"
+#include <quiche.h>
+
+class UnixNetVConnection;
+struct NetAccept;
+
+//////////////////////////////////////////////////////////////////
+//
+//  class QUICNetProcessor
+//
+//////////////////////////////////////////////////////////////////
+class QUICNetProcessor : public UnixNetProcessor
+{
+public:
+  QUICNetProcessor();
+  virtual ~QUICNetProcessor();
+
+  void init() override;
+  virtual int start(int, size_t stacksize) override;
+  // TODO: refactoring NetProcessor::connect_re and UnixNetProcessor::connect_re_internal
+  // Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts) override;
+  Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts);
+
+  virtual NetAccept *createNetAccept(const NetProcessor::AcceptOptions &opt) override;
+  virtual NetVConnection *allocate_vc(EThread *t) override;
+
+  Action *main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt) override;
+
+  off_t quicPollCont_offset;
+
+private:
+  QUICNetProcessor(const QUICNetProcessor &);
+  QUICNetProcessor &operator=(const QUICNetProcessor &);
+
+  QUICConnectionTable *_ctable  = nullptr;
+  quiche_config *_quiche_config = nullptr;
+};
+
+extern QUICNetProcessor quic_NetProcessor;
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h
index c01d775..53922bd 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection.h
@@ -21,382 +21,12 @@
   limitations under the License.
  */
 
-/****************************************************************************
-
-  QUICNetVConnection.h
-
-  This file implements an I/O Processor for network I/O.
-
-
- ****************************************************************************/
 #pragma once
 
-#include "tscore/ink_platform.h"
-#include "P_Net.h"
-#include "P_EventSystem.h"
-#include "P_UnixNetVConnection.h"
-#include "P_UnixNet.h"
-#include "P_UDPNet.h"
-#include "P_ALPNSupport.h"
-#include "TLSBasicSupport.h"
-#include "TLSSessionResumptionSupport.h"
-#include "tscore/ink_apidefs.h"
-#include "tscore/List.h"
+#include "tscore/ink_config.h"
 
-#include "quic/QUICConfig.h"
-#include "quic/QUICConnection.h"
-#include "quic/QUICConnectionTable.h"
-#include "quic/QUICResetTokenTable.h"
-#include "quic/QUICVersionNegotiator.h"
-#include "quic/QUICPacket.h"
-#include "quic/QUICPacketFactory.h"
-#include "quic/QUICFrame.h"
-#include "quic/QUICFrameDispatcher.h"
-#include "quic/QUICApplication.h"
-#include "quic/QUICStream.h"
-#include "quic/QUICHandshakeProtocol.h"
-#include "quic/QUICAckFrameCreator.h"
-#include "quic/QUICPinger.h"
-#include "quic/QUICPadder.h"
-#include "quic/QUICLossDetector.h"
-#include "quic/QUICStreamManager.h"
-#include "quic/QUICAltConnectionManager.h"
-#include "quic/QUICPathValidator.h"
-#include "quic/QUICPathManager.h"
-#include "quic/QUICApplicationMap.h"
-#include "quic/QUICPacketReceiveQueue.h"
-#include "quic/QUICPacketHeaderProtector.h"
-#include "quic/QUICAddrVerifyState.h"
-#include "quic/QUICPacketProtectionKeyInfo.h"
-#include "quic/QUICContext.h"
-#include "quic/QUICTokenCreator.h"
-#include "quic/qlog/QLogListener.h"
-
-// Size of connection ids for debug log : e.g. aaaaaaaa-bbbbbbbb\0
-static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1;
-
-//////////////////////////////////////////////////////////////////
-//
-//  class NetVConnection
-//
-//  A VConnection for a network socket.
-//
-//////////////////////////////////////////////////////////////////
-
-class QUICPacketHandler;
-class QUICLossDetector;
-class QUICHandshake;
-
-class SSLNextProtocolSet;
-
-/**
- * @class QUICNetVConnection
- * @brief A NetVConnection for a QUIC network socket
- * @detail
- *
- * state_pre_handshake()
- *  | READ:
- *  |   Do nothing
- *  | WRITE:
- *  |   _state_common_send_packet()
- *  v
- * state_handshake()
- *  | READ:
- *  |   _state_handshake_process_packet()
- *  |   _state_handshake_process_initial_packet()
- *  |   _state_handshake_process_retry_packet()
- *  |   _state_handshake_process_handshake_packet()
- *  |   _state_handshake_process_zero_rtt_protected_packet()
- *  | WRITE:
- *  |   _state_common_send_packet()
- *  |   or
- *  |   _state_handshake_send_retry_packet()
- *  v
- * state_connection_established()
- *  | READ:
- *  |   _state_connection_established_receive_packet()
- *  |   _state_connection_established_process_protected_packet()
- *  | WRITE:
- *  |   _state_common_send_packet()
- *  v
- * state_connection_closing() (If closing actively)
- *  | READ:
- *  |   _state_closing_receive_packet()
- *  | WRITE:
- *  |   _state_closing_send_packet()
- *  v
- * state_connection_draining() (If closing passively)
- *  | READ:
- *  |   _state_draining_receive_packet()
- *  | WRITE:
- *  |   Do nothing
- *  v
- * state_connection_close()
- *    READ:
- *      Do nothing
- *    WRITE:
- *      Do nothing
- **/
-class QUICNetVConnection : public UnixNetVConnection,
-                           public QUICConnection,
-                           public RefCountObj,
-                           public ALPNSupport,
-                           public TLSBasicSupport,
-                           public TLSSessionResumptionSupport
-{
-  using super = UnixNetVConnection; ///< Parent type.
-
-public:
-  QUICNetVConnection();
-  ~QUICNetVConnection();
-  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *,
-            QUICResetTokenTable *rtable);
-  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
-            QUICConnectionId retry_cid, UDPConnection *, QUICPacketHandler *, QUICResetTokenTable *rtable,
-            QUICConnectionTable *ctable);
-
-  // accept new conn_id
-  int acceptEvent(int event, Event *e);
-
-  // NetVConnection
-  void set_local_addr() override;
-
-  // UnixNetVConnection
-  void reenable(VIO *vio) override;
-  VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override;
-  VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override;
-  int connectUp(EThread *t, int fd) override;
-
-  // QUICNetVConnection
-  int startEvent(int event, Event *e);
-  int state_pre_handshake(int event, Event *data);
-  int state_handshake(int event, Event *data);
-  int state_connection_established(int event, Event *data);
-  int state_connection_closing(int event, Event *data);
-  int state_connection_draining(int event, Event *data);
-  int state_connection_closed(int event, Event *data);
-  void start();
-  void remove_connection_ids();
-  void free(EThread *t) override;
-  void free() override;
-  void destroy(EThread *t);
-
-  UDPConnection *get_udp_con();
-  virtual void net_read_io(NetHandler *nh, EThread *lthread) override;
-  virtual int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override;
-
-  int populate_protocol(std::string_view *results, int n) const override;
-  const char *protocol_contains(std::string_view tag) const override;
-
-  // QUICConnection
-  QUICStreamManager *stream_manager() override;
-  void close_quic_connection(QUICConnectionErrorUPtr error) override;
-  void reset_quic_connection() override;
-  void handle_received_packet(UDPPacket *packet) override;
-  void ping() override;
-
-  // QUICConnection (QUICConnectionInfoProvider)
-  QUICConnectionId peer_connection_id() const override;
-  QUICConnectionId original_connection_id() const override;
-  QUICConnectionId first_connection_id() const override;
-  QUICConnectionId retry_source_connection_id() const override;
-  QUICConnectionId initial_source_connection_id() const override;
-  QUICConnectionId connection_id() const override;
-  std::string_view cids() const override;
-  const QUICFiveTuple five_tuple() const override;
-  uint32_t pmtu() const override;
-  NetVConnectionContext_t direction() const override;
-  QUICVersion negotiated_version() const override;
-  std::string_view negotiated_application_name() const override;
-  bool is_closed() const override;
-  bool is_at_anti_amplification_limit() const override;
-  bool is_address_validation_completed() const override;
-  bool is_handshake_completed() const override;
-  bool has_keys_for(QUICPacketNumberSpace space) const override;
-
-  // QUICConnection (QUICFrameHandler)
-  std::vector<QUICFrameType> interests() override;
-  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
-
-  int in_closed_queue = 0;
-
-  bool shouldDestroy();
-
-  LINK(QUICNetVConnection, closed_link);
-  SLINK(QUICNetVConnection, closed_alink);
-
-protected:
-  // TLSBasicSupport
-  SSL *_get_ssl_object() const override;
-  ssl_curve_id _get_tls_curve() const override;
-
-  // TLSSessionResumptionSupport
-  const IpEndpoint &_getLocalEndpoint() override;
-
-private:
-  std::random_device _rnd;
-
-  QUICConfig::scoped_config _quic_config;
-
-  QUICConnectionId _peer_quic_connection_id;      // dst cid in local
-  QUICConnectionId _peer_old_quic_connection_id;  // dst previous cid in local
-  QUICConnectionId _original_quic_connection_id;  // dst cid of initial packet from client
-  QUICConnectionId _first_quic_connection_id;     // dst cid of initial packet from client that doesn't have retry token
-  QUICConnectionId _retry_source_connection_id;   // src cid used for sending Retry packet
-  QUICConnectionId _initial_source_connection_id; // src cid used for Initial packet
-  QUICConnectionId _quic_connection_id;           // src cid in local
-  QUICFiveTuple _five_tuple;
-  bool _connection_migration_initiated = false;
-
-  char _cids_data[MAX_CIDS_SIZE] = {0};
-  std::string_view _cids;
-
-  QUICVersion _initial_version;
-  UDPConnection *_udp_con = nullptr;
-  QUICPacketProtectionKeyInfo _pp_key_info;
-  QUICPacketHandler *_packet_handler = nullptr;
-  QUICPacketFactory _packet_factory;
-  QUICFrameFactory _frame_factory;
-  QUICAckFrameManager _ack_frame_manager;
-  QUICPacketHeaderProtector _ph_protector;
-  QUICRTTMeasure _rtt_measure;
-  QUICApplicationMap *_application_map = nullptr;
-
-  uint32_t _pmtu = 1280;
-
-  // TODO: use custom allocator and make them std::unique_ptr or std::shared_ptr
-  // or make them just member variables.
-  QUICPinger *_pinger                               = nullptr;
-  QUICPadder *_padder                               = nullptr;
-  QUICHandshake *_handshake_handler                 = nullptr;
-  QUICHandshakeProtocol *_hs_protocol               = nullptr;
-  QUICLossDetector *_loss_detector                  = nullptr;
-  QUICFrameDispatcher *_frame_dispatcher            = nullptr;
-  QUICStreamManager *_stream_manager                = nullptr;
-  QUICCongestionController *_congestion_controller  = nullptr;
-  QUICRemoteFlowController *_remote_flow_controller = nullptr;
-  QUICLocalFlowController *_local_flow_controller   = nullptr;
-  QUICResetTokenTable *_rtable                      = nullptr;
-  QUICConnectionTable *_ctable                      = nullptr;
-  QUICAltConnectionManager *_alt_con_manager        = nullptr;
-  QUICPathValidator *_path_validator                = nullptr;
-  QUICPathManager *_path_manager                    = nullptr;
-  QUICTokenCreator *_token_creator                  = nullptr;
-
-  QUICFrameGeneratorManager _frame_generators;
-
-  QUICPacketReceiveQueue _packet_recv_queue = {this->_packet_factory, this->_ph_protector};
-
-  QUICConnectionErrorUPtr _connection_error  = nullptr;
-  uint32_t _state_closing_recv_packet_count  = 0;
-  uint32_t _state_closing_recv_packet_window = 1;
-  uint64_t _flow_control_buffer_size         = 1024;
-
-  void _init_submodules();
-
-  void _schedule_packet_write_ready(bool delay = false);
-  void _unschedule_packet_write_ready();
-  void _close_packet_write_ready(Event *data);
-  Event *_packet_write_ready = nullptr;
-
-  void _schedule_closing_timeout(ink_hrtime interval);
-  void _unschedule_closing_timeout();
-  void _close_closing_timeout(Event *data);
-  Event *_closing_timeout = nullptr;
-
-  void _schedule_closed_event();
-  void _unschedule_closed_event();
-  void _close_closed_event(Event *data);
-  Event *_closed_event = nullptr;
-
-  void _schedule_ack_manager_periodic(ink_hrtime interval);
-  void _unschedule_ack_manager_periodic();
-  Event *_ack_manager_periodic = nullptr;
-
-  QUICEncryptionLevel _minimum_encryption_level = QUICEncryptionLevel::INITIAL;
-
-  QUICPacketNumber _largest_acked_packet_number(QUICEncryptionLevel level) const;
-  uint32_t _maximum_quic_packet_size() const;
-  uint32_t _minimum_quic_packet_size();
-  uint64_t _maximum_stream_frame_data_size();
-
-  Ptr<IOBufferBlock> _store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame,
-                                  std::vector<QUICSentPacketInfo::FrameInfo> &frames);
-  QUICPacketUPtr _packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
-                                   std::vector<QUICSentPacketInfo::FrameInfo> &frames);
-  void _packetize_closing_frame();
-  QUICPacketUPtr _build_packet(uint8_t *packet_buf, QUICEncryptionLevel level, const Ptr<IOBufferBlock> &parent_block,
-                               bool retransmittable, bool probing, bool crypto);
-
-  QUICConnectionErrorUPtr _recv_and_ack(const QUICPacketR &packet, bool *has_non_probing_frame = nullptr);
-
-  QUICConnectionErrorUPtr _state_handshake_process_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_version_negotiation_packet(const QUICVersionNegotiationPacketR &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_initial_packet(const QUICInitialPacketR &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_retry_packet(const QUICRetryPacketR &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_handshake_packet(const QUICHandshakePacketR &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_zero_rtt_protected_packet(const QUICZeroRttPacketR &packet);
-  QUICConnectionErrorUPtr _state_connection_established_receive_packet();
-  QUICConnectionErrorUPtr _state_connection_established_process_protected_packet(const QUICShortHeaderPacketR &packet);
-  QUICConnectionErrorUPtr _state_connection_established_migrate_connection(const QUICPacketR &packet);
-  QUICConnectionErrorUPtr _state_connection_established_initiate_connection_migration();
-  QUICConnectionErrorUPtr _state_closing_receive_packet();
-  QUICConnectionErrorUPtr _state_draining_receive_packet();
-  QUICConnectionErrorUPtr _state_common_send_packet();
-  QUICConnectionErrorUPtr _state_handshake_send_retry_packet();
-  QUICConnectionErrorUPtr _state_closing_send_packet();
-
-  Ptr<ProxyMutex> _packet_transmitter_mutex;
-
-  void _init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                 const std::shared_ptr<const QUICTransportParameters> &remote_tp);
-  void _handle_error(QUICConnectionErrorUPtr error);
-  QUICPacketUPtr _dequeue_recv_packet(uint8_t *packet_buf, QUICPacketCreationResult &result);
-  void _validate_new_path(const QUICPath &path);
-
-  bool _handshake_completed = false;
-  int _complete_handshake_if_possible();
-
-  void _switch_to_handshake_state();
-  void _switch_to_established_state();
-  void _switch_to_closing_state(QUICConnectionErrorUPtr error);
-  void _switch_to_draining_state(QUICConnectionErrorUPtr error);
-  void _switch_to_close_state();
-
-  bool _application_started = false;
-  void _start_application();
-
-  void _handle_periodic_ack_event();
-  void _handle_idle_timeout();
-  void _handle_active_timeout();
-
-  QUICConnectionErrorUPtr _handle_frame(const QUICNewConnectionIdFrame &frame);
-
-  void _update_cids();
-  void _update_peer_cid(const QUICConnectionId &new_cid);
-  void _update_local_cid(const QUICConnectionId &new_cid);
-  void _rerandomize_original_cid();
-
-  QUICHandshakeProtocol *_setup_handshake_protocol(const shared_SSL_CTX &ctx);
-
-  QUICPacketUPtr _the_final_packet = QUICPacketFactory::create_null_packet();
-  uint8_t _final_packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
-  QUICStatelessResetToken _reset_token;
-
-  ats_unique_buf _av_token = {nullptr};
-  size_t _av_token_len     = 0;
-
-  uint64_t _stream_frames_sent = 0;
-  uint32_t _seq_num            = 0;
-
-  // TODO: Source addresses verification through an address validation token
-  QUICAddrVerifyState _verified_state;
-
-  std::unique_ptr<QUICContext> _context;
-
-  std::shared_ptr<QLog::QLogListener> _qlog;
-};
-
-typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *);
-
-extern ClassAllocator<QUICNetVConnection> quicNetVCAllocator;
+#if HAVE_QUICHE_H
+#include "P_QUICNetVConnection_quiche.h"
+#else
+#include "P_QUICNetVConnection_native.h"
+#endif
diff --git a/iocore/net/P_QUICNetVConnection_native.h b/iocore/net/P_QUICNetVConnection_native.h
new file mode 100644
index 0000000..763b5b0
--- /dev/null
+++ b/iocore/net/P_QUICNetVConnection_native.h
@@ -0,0 +1,400 @@
+/** @file
+
+  A brief file description
+
+  @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.
+ */
+
+/****************************************************************************
+
+  QUICNetVConnection.h
+
+  This file implements an I/O Processor for network I/O.
+
+
+ ****************************************************************************/
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Net.h"
+#include "P_EventSystem.h"
+#include "P_UnixNetVConnection.h"
+#include "P_UnixNet.h"
+#include "P_UDPNet.h"
+#include "P_ALPNSupport.h"
+#include "TLSBasicSupport.h"
+#include "TLSSessionResumptionSupport.h"
+#include "tscore/ink_apidefs.h"
+#include "tscore/List.h"
+
+#include "quic/QUICConfig.h"
+#include "quic/QUICConnection.h"
+#include "quic/QUICConnectionTable.h"
+#include "quic/QUICResetTokenTable.h"
+#include "quic/QUICVersionNegotiator.h"
+#include "quic/QUICPacket.h"
+#include "quic/QUICPacketFactory.h"
+#include "quic/QUICFrame.h"
+#include "quic/QUICFrameDispatcher.h"
+#include "quic/QUICApplication.h"
+#include "quic/QUICStream.h"
+#include "quic/QUICHandshakeProtocol.h"
+#include "quic/QUICAckFrameCreator.h"
+#include "quic/QUICPinger.h"
+#include "quic/QUICPadder.h"
+#include "quic/QUICLossDetector.h"
+#include "quic/QUICStreamManager_native.h"
+#include "quic/QUICAltConnectionManager.h"
+#include "quic/QUICPathValidator.h"
+#include "quic/QUICPathManager.h"
+#include "quic/QUICApplicationMap.h"
+#include "quic/QUICPacketReceiveQueue.h"
+#include "quic/QUICPacketHeaderProtector.h"
+#include "quic/QUICAddrVerifyState.h"
+#include "quic/QUICPacketProtectionKeyInfo.h"
+#include "quic/QUICContext.h"
+#include "quic/QUICTokenCreator.h"
+#include "quic/qlog/QLogListener.h"
+
+// Size of connection ids for debug log : e.g. aaaaaaaa-bbbbbbbb\0
+static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1;
+
+//////////////////////////////////////////////////////////////////
+//
+//  class NetVConnection
+//
+//  A VConnection for a network socket.
+//
+//////////////////////////////////////////////////////////////////
+
+class QUICPacketHandler;
+class QUICLossDetector;
+class QUICHandshake;
+
+class SSLNextProtocolSet;
+
+/**
+ * @class QUICNetVConnection
+ * @brief A NetVConnection for a QUIC network socket
+ * @detail
+ *
+ * state_pre_handshake()
+ *  | READ:
+ *  |   Do nothing
+ *  | WRITE:
+ *  |   _state_common_send_packet()
+ *  v
+ * state_handshake()
+ *  | READ:
+ *  |   _state_handshake_process_packet()
+ *  |   _state_handshake_process_initial_packet()
+ *  |   _state_handshake_process_retry_packet()
+ *  |   _state_handshake_process_handshake_packet()
+ *  |   _state_handshake_process_zero_rtt_protected_packet()
+ *  | WRITE:
+ *  |   _state_common_send_packet()
+ *  |   or
+ *  |   _state_handshake_send_retry_packet()
+ *  v
+ * state_connection_established()
+ *  | READ:
+ *  |   _state_connection_established_receive_packet()
+ *  |   _state_connection_established_process_protected_packet()
+ *  | WRITE:
+ *  |   _state_common_send_packet()
+ *  v
+ * state_connection_closing() (If closing actively)
+ *  | READ:
+ *  |   _state_closing_receive_packet()
+ *  | WRITE:
+ *  |   _state_closing_send_packet()
+ *  v
+ * state_connection_draining() (If closing passively)
+ *  | READ:
+ *  |   _state_draining_receive_packet()
+ *  | WRITE:
+ *  |   Do nothing
+ *  v
+ * state_connection_close()
+ *    READ:
+ *      Do nothing
+ *    WRITE:
+ *      Do nothing
+ **/
+class QUICNetVConnection : public UnixNetVConnection,
+                           public QUICConnection,
+                           public RefCountObj,
+                           public ALPNSupport,
+                           public TLSBasicSupport,
+                           public TLSSessionResumptionSupport
+{
+  using super = UnixNetVConnection; ///< Parent type.
+
+public:
+  QUICNetVConnection();
+  ~QUICNetVConnection();
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *,
+            QUICResetTokenTable *rtable);
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+            QUICConnectionId retry_cid, UDPConnection *, QUICPacketHandler *, QUICResetTokenTable *rtable,
+            QUICConnectionTable *ctable);
+
+  // accept new conn_id
+  int acceptEvent(int event, Event *e);
+
+  // NetVConnection
+  void set_local_addr() override;
+
+  // UnixNetVConnection
+  void reenable(VIO *vio) override;
+  VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override;
+  VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override;
+  int connectUp(EThread *t, int fd) override;
+
+  // QUICNetVConnection
+  int startEvent(int event, Event *e);
+  int state_pre_handshake(int event, Event *data);
+  int state_handshake(int event, Event *data);
+  int state_connection_established(int event, Event *data);
+  int state_connection_closing(int event, Event *data);
+  int state_connection_draining(int event, Event *data);
+  int state_connection_closed(int event, Event *data);
+  void start();
+  void remove_connection_ids();
+  void free(EThread *t) override;
+  void free() override;
+  void destroy(EThread *t);
+
+  UDPConnection *get_udp_con();
+  virtual void net_read_io(NetHandler *nh, EThread *lthread) override;
+  virtual int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override;
+
+  int populate_protocol(std::string_view *results, int n) const override;
+  const char *protocol_contains(std::string_view tag) const override;
+
+  // QUICConnection
+  QUICStreamManager *stream_manager() override;
+  void close_quic_connection(QUICConnectionErrorUPtr error) override;
+  void reset_quic_connection() override;
+  void handle_received_packet(UDPPacket *packet) override;
+  void ping() override;
+
+  // QUICConnection (QUICConnectionInfoProvider)
+  QUICConnectionId peer_connection_id() const override;
+  QUICConnectionId original_connection_id() const override;
+  QUICConnectionId first_connection_id() const override;
+  QUICConnectionId retry_source_connection_id() const override;
+  QUICConnectionId initial_source_connection_id() const override;
+  QUICConnectionId connection_id() const override;
+  std::string_view cids() const override;
+  const QUICFiveTuple five_tuple() const override;
+  uint32_t pmtu() const override;
+  NetVConnectionContext_t direction() const override;
+  QUICVersion negotiated_version() const override;
+  std::string_view negotiated_application_name() const override;
+  bool is_closed() const override;
+  bool is_at_anti_amplification_limit() const override;
+  bool is_address_validation_completed() const override;
+  bool is_handshake_completed() const override;
+  bool has_keys_for(QUICPacketNumberSpace space) const override;
+
+  // QUICConnection (QUICFrameHandler)
+  std::vector<QUICFrameType> interests() override;
+  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
+
+  int in_closed_queue = 0;
+
+  bool shouldDestroy();
+
+  LINK(QUICNetVConnection, closed_link);
+  SLINK(QUICNetVConnection, closed_alink);
+
+protected:
+  // TLSBasicSupport
+  SSL *_get_ssl_object() const override;
+  ssl_curve_id _get_tls_curve() const override;
+
+  // TLSSessionResumptionSupport
+  const IpEndpoint &_getLocalEndpoint() override;
+
+private:
+  std::random_device _rnd;
+
+  QUICConfig::scoped_config _quic_config;
+
+  QUICConnectionId _peer_quic_connection_id;      // dst cid in local
+  QUICConnectionId _peer_old_quic_connection_id;  // dst previous cid in local
+  QUICConnectionId _original_quic_connection_id;  // dst cid of initial packet from client
+  QUICConnectionId _first_quic_connection_id;     // dst cid of initial packet from client that doesn't have retry token
+  QUICConnectionId _retry_source_connection_id;   // src cid used for sending Retry packet
+  QUICConnectionId _initial_source_connection_id; // src cid used for Initial packet
+  QUICConnectionId _quic_connection_id;           // src cid in local
+  QUICFiveTuple _five_tuple;
+  bool _connection_migration_initiated = false;
+
+  char _cids_data[MAX_CIDS_SIZE] = {0};
+  std::string_view _cids;
+
+  QUICVersion _initial_version;
+  UDPConnection *_udp_con = nullptr;
+  QUICPacketProtectionKeyInfo _pp_key_info;
+  QUICPacketHandler *_packet_handler = nullptr;
+  QUICPacketFactory _packet_factory;
+  QUICFrameFactory _frame_factory;
+  QUICAckFrameManager _ack_frame_manager;
+  QUICPacketHeaderProtector _ph_protector;
+  QUICRTTMeasure _rtt_measure;
+  QUICApplicationMap *_application_map = nullptr;
+
+  uint32_t _pmtu = 1280;
+
+  // TODO: use custom allocator and make them std::unique_ptr or std::shared_ptr
+  // or make them just member variables.
+  QUICPinger *_pinger                               = nullptr;
+  QUICPadder *_padder                               = nullptr;
+  QUICHandshake *_handshake_handler                 = nullptr;
+  QUICHandshakeProtocol *_hs_protocol               = nullptr;
+  QUICLossDetector *_loss_detector                  = nullptr;
+  QUICFrameDispatcher *_frame_dispatcher            = nullptr;
+  QUICStreamManagerImpl *_stream_manager            = nullptr;
+  QUICCongestionController *_congestion_controller  = nullptr;
+  QUICRemoteFlowController *_remote_flow_controller = nullptr;
+  QUICLocalFlowController *_local_flow_controller   = nullptr;
+  QUICResetTokenTable *_rtable                      = nullptr;
+  QUICConnectionTable *_ctable                      = nullptr;
+  QUICAltConnectionManager *_alt_con_manager        = nullptr;
+  QUICPathValidator *_path_validator                = nullptr;
+  QUICPathManager *_path_manager                    = nullptr;
+  QUICTokenCreator *_token_creator                  = nullptr;
+
+  QUICFrameGeneratorManager _frame_generators;
+
+  QUICPacketReceiveQueue _packet_recv_queue = {this->_packet_factory, this->_ph_protector};
+
+  QUICConnectionErrorUPtr _connection_error  = nullptr;
+  uint32_t _state_closing_recv_packet_count  = 0;
+  uint32_t _state_closing_recv_packet_window = 1;
+  uint64_t _flow_control_buffer_size         = 1024;
+
+  void _init_submodules();
+
+  void _schedule_packet_write_ready(bool delay = false);
+  void _unschedule_packet_write_ready();
+  void _close_packet_write_ready(Event *data);
+  Event *_packet_write_ready = nullptr;
+
+  void _schedule_closing_timeout(ink_hrtime interval);
+  void _unschedule_closing_timeout();
+  void _close_closing_timeout(Event *data);
+  Event *_closing_timeout = nullptr;
+
+  void _schedule_closed_event();
+  void _unschedule_closed_event();
+  void _close_closed_event(Event *data);
+  Event *_closed_event = nullptr;
+
+  void _schedule_ack_manager_periodic(ink_hrtime interval);
+  void _unschedule_ack_manager_periodic();
+  Event *_ack_manager_periodic = nullptr;
+
+  QUICEncryptionLevel _minimum_encryption_level = QUICEncryptionLevel::INITIAL;
+
+  QUICPacketNumber _largest_acked_packet_number(QUICEncryptionLevel level) const;
+  uint32_t _maximum_quic_packet_size() const;
+  uint32_t _minimum_quic_packet_size();
+  uint64_t _maximum_stream_frame_data_size();
+
+  Ptr<IOBufferBlock> _store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame,
+                                  std::vector<QUICSentPacketInfo::FrameInfo> &frames);
+  QUICPacketUPtr _packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
+                                   std::vector<QUICSentPacketInfo::FrameInfo> &frames);
+  void _packetize_closing_frame();
+  QUICPacketUPtr _build_packet(uint8_t *packet_buf, QUICEncryptionLevel level, const Ptr<IOBufferBlock> &parent_block,
+                               bool retransmittable, bool probing, bool crypto);
+
+  QUICConnectionErrorUPtr _recv_and_ack(const QUICPacketR &packet, bool *has_non_probing_frame = nullptr);
+
+  QUICConnectionErrorUPtr _state_handshake_process_packet(const QUICPacket &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_version_negotiation_packet(const QUICVersionNegotiationPacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_initial_packet(const QUICInitialPacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_retry_packet(const QUICRetryPacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_handshake_packet(const QUICHandshakePacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_zero_rtt_protected_packet(const QUICZeroRttPacketR &packet);
+  QUICConnectionErrorUPtr _state_connection_established_receive_packet();
+  QUICConnectionErrorUPtr _state_connection_established_process_protected_packet(const QUICShortHeaderPacketR &packet);
+  QUICConnectionErrorUPtr _state_connection_established_migrate_connection(const QUICPacketR &packet);
+  QUICConnectionErrorUPtr _state_connection_established_initiate_connection_migration();
+  QUICConnectionErrorUPtr _state_closing_receive_packet();
+  QUICConnectionErrorUPtr _state_draining_receive_packet();
+  QUICConnectionErrorUPtr _state_common_send_packet();
+  QUICConnectionErrorUPtr _state_handshake_send_retry_packet();
+  QUICConnectionErrorUPtr _state_closing_send_packet();
+
+  void _init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                 const std::shared_ptr<const QUICTransportParameters> &remote_tp);
+  void _handle_error(QUICConnectionErrorUPtr error);
+  QUICPacketUPtr _dequeue_recv_packet(uint8_t *packet_buf, QUICPacketCreationResult &result);
+  void _validate_new_path(const QUICPath &path);
+
+  bool _handshake_completed = false;
+  int _complete_handshake_if_possible();
+
+  void _switch_to_handshake_state();
+  void _switch_to_established_state();
+  void _switch_to_closing_state(QUICConnectionErrorUPtr error);
+  void _switch_to_draining_state(QUICConnectionErrorUPtr error);
+  void _switch_to_close_state();
+
+  bool _application_started = false;
+  void _start_application();
+
+  void _handle_periodic_ack_event();
+  void _handle_idle_timeout();
+  void _handle_active_timeout();
+
+  QUICConnectionErrorUPtr _handle_frame(const QUICNewConnectionIdFrame &frame);
+
+  void _update_cids();
+  void _update_peer_cid(const QUICConnectionId &new_cid);
+  void _update_local_cid(const QUICConnectionId &new_cid);
+  void _rerandomize_original_cid();
+
+  QUICHandshakeProtocol *_setup_handshake_protocol(const shared_SSL_CTX &ctx);
+
+  QUICPacketUPtr _the_final_packet = QUICPacketFactory::create_null_packet();
+  uint8_t _final_packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+  QUICStatelessResetToken _reset_token;
+
+  ats_unique_buf _av_token = {nullptr};
+  size_t _av_token_len     = 0;
+
+  uint64_t _stream_frames_sent = 0;
+  uint32_t _seq_num            = 0;
+
+  // TODO: Source addresses verification through an address validation token
+  QUICAddrVerifyState _verified_state;
+
+  std::unique_ptr<QUICContext> _context;
+
+  std::shared_ptr<QLog::QLogListener> _qlog;
+};
+
+typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *);
+
+extern ClassAllocator<QUICNetVConnection> quicNetVCAllocator;
diff --git a/iocore/net/P_QUICNetVConnection_quiche.h b/iocore/net/P_QUICNetVConnection_quiche.h
new file mode 100644
index 0000000..400d363
--- /dev/null
+++ b/iocore/net/P_QUICNetVConnection_quiche.h
@@ -0,0 +1,181 @@
+/** @file
+
+  A brief file description
+
+  @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.
+ */
+
+/****************************************************************************
+
+  QUICNetVConnection_quiche.h
+
+  This file implements an I/O Processor for network I/O.
+
+
+ ****************************************************************************/
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Net.h"
+#include "P_EventSystem.h"
+#include "P_UnixNetVConnection.h"
+#include "P_UnixNet.h"
+#include "P_UDPNet.h"
+#include "P_ALPNSupport.h"
+#include "TLSBasicSupport.h"
+#include "tscore/ink_apidefs.h"
+#include "tscore/List.h"
+
+#include "quic/QUICConnection.h"
+#include "quic/QUICConnectionTable.h"
+#include "quic/QUICContext.h"
+#include "quic/QUICStreamManager.h"
+#include "quic/QUICStreamManager_quiche.h"
+#include <quiche.h>
+
+class QUICPacketHandler;
+class QUICResetTokenTable;
+class QUICConnectionTable;
+
+class QUICNetVConnection : public UnixNetVConnection,
+                           public QUICConnection,
+                           public RefCountObj,
+                           public ALPNSupport,
+                           public TLSBasicSupport
+{
+  using super = UnixNetVConnection; ///< Parent type.
+
+public:
+  QUICNetVConnection();
+  ~QUICNetVConnection();
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *);
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+            QUICConnectionId retry_cid, UDPConnection *, quiche_conn *, QUICPacketHandler *, QUICConnectionTable *ctable);
+
+  // Event handlers
+  int acceptEvent(int event, Event *e);
+  int state_handshake(int event, Event *e);
+  int state_established(int event, Event *e);
+
+  // RefCountObj
+  void free() override;
+
+  // NetVConnection
+  void set_local_addr() override;
+
+  // NetEvent
+  void free(EThread *t) override;
+
+  // UnixNetVConnection
+  void reenable(VIO *vio) override;
+  VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override;
+  VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override;
+  int connectUp(EThread *t, int fd) override;
+  int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override;
+
+  // NetEvent
+  virtual void net_read_io(NetHandler *nh, EThread *lthread) override;
+
+  // NetVConnection
+  int populate_protocol(std::string_view *results, int n) const override;
+  const char *protocol_contains(std::string_view tag) const override;
+
+  // QUICConnection
+  QUICStreamManager *stream_manager() override;
+  void close_quic_connection(QUICConnectionErrorUPtr error) override;
+  void reset_quic_connection() override;
+  void handle_received_packet(UDPPacket *packet) override;
+  void ping() override;
+
+  // QUICConnection (QUICConnectionInfoProvider)
+  QUICConnectionId peer_connection_id() const override;
+  QUICConnectionId original_connection_id() const override;
+  QUICConnectionId first_connection_id() const override;
+  QUICConnectionId retry_source_connection_id() const override;
+  QUICConnectionId initial_source_connection_id() const override;
+  QUICConnectionId connection_id() const override;
+  std::string_view cids() const override;
+  const QUICFiveTuple five_tuple() const override;
+  uint32_t pmtu() const override;
+  NetVConnectionContext_t direction() const override;
+  QUICVersion negotiated_version() const override;
+  std::string_view negotiated_application_name() const override;
+  bool is_closed() const override;
+  bool is_at_anti_amplification_limit() const override;
+  bool is_address_validation_completed() const override;
+  bool is_handshake_completed() const override;
+  bool has_keys_for(QUICPacketNumberSpace space) const override;
+
+  // QUICConnection (QUICFrameHandler)
+  std::vector<QUICFrameType> interests() override;
+  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
+
+  // QUICNetVConnection
+  int in_closed_queue = 0;
+
+  bool shouldDestroy();
+  void destroy(EThread *t);
+  void remove_connection_ids();
+
+  LINK(QUICNetVConnection, closed_link);
+  SLINK(QUICNetVConnection, closed_alink);
+
+protected:
+  std::unique_ptr<QUICContext> _context;
+  QUICPacketHandler *_packet_handler = nullptr;
+
+  // TLSBasicSupport
+  SSL *_get_ssl_object() const override;
+  ssl_curve_id _get_tls_curve() const override;
+
+private:
+  QUICConfig::scoped_config _quic_config;
+
+  QUICConnectionId _peer_quic_connection_id;      // dst cid in local
+  QUICConnectionId _peer_old_quic_connection_id;  // dst previous cid in local
+  QUICConnectionId _original_quic_connection_id;  // dst cid of initial packet from client
+  QUICConnectionId _first_quic_connection_id;     // dst cid of initial packet from client that doesn't have retry token
+  QUICConnectionId _retry_source_connection_id;   // src cid used for sending Retry packet
+  QUICConnectionId _initial_source_connection_id; // src cid used for Initial packet
+  QUICConnectionId _quic_connection_id;           // src cid in local
+
+  UDPConnection *_udp_con      = nullptr;
+  quiche_conn *_quiche_con     = nullptr;
+  QUICConnectionTable *_ctable = nullptr;
+
+  void _schedule_packet_write_ready(bool delay = false);
+  void _unschedule_packet_write_ready();
+  void _close_packet_write_ready(Event *data);
+  Event *_packet_write_ready = nullptr;
+
+  void _handle_read_ready();
+  void _handle_write_ready();
+  void _handle_interval();
+
+  void _switch_to_established_state();
+
+  bool _handshake_completed = false;
+  bool _application_started = false;
+  void _start_application();
+
+  QUICStreamManagerImpl *_stream_manager = nullptr;
+  QUICApplicationMap *_application_map   = nullptr;
+};
+
+extern ClassAllocator<QUICNetVConnection> quicNetVCAllocator;
diff --git a/iocore/net/P_QUICNextProtocolAccept.h b/iocore/net/P_QUICNextProtocolAccept.h
index 3ca44db..a950fa1 100644
--- a/iocore/net/P_QUICNextProtocolAccept.h
+++ b/iocore/net/P_QUICNextProtocolAccept.h
@@ -1,6 +1,6 @@
 /** @file
 
-  QUICNextProtocolAccept
+  A brief file description
 
   @section license License
 
@@ -23,37 +23,10 @@
 
 #pragma once
 
-#include "P_QUICNetVConnection.h"
-#include "P_SSLNextProtocolSet.h"
-#include "I_IOBuffer.h"
+#include "tscore/ink_config.h"
 
-class QUICNextProtocolAccept : public SessionAccept
-{
-public:
-  QUICNextProtocolAccept();
-  ~QUICNextProtocolAccept();
-
-  bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *) override;
-
-  // Register handler as an endpoint for the specified protocol. Neither
-  // handler nor protocol are copied, so the caller must guarantee their
-  // lifetime is at least as long as that of the acceptor.
-  bool registerEndpoint(const char *protocol, Continuation *handler);
-
-  void enableProtocols(const SessionProtocolSet &protos);
-
-  SLINK(QUICNextProtocolAccept, link);
-  SSLNextProtocolSet *getProtoSet();
-
-  // noncopyable
-  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;            // disabled
-  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete; // disabled
-
-private:
-  int mainEvent(int event, void *netvc) override;
-
-  SSLNextProtocolSet protoset;
-  SessionProtocolSet protoenabled;
-
-  friend struct QUICNextProtocolTrampoline;
-};
+#if HAVE_QUICHE_H
+#include "P_QUICNextProtocolAccept_quiche.h"
+#else
+#include "P_QUICNextProtocolAccept_native.h"
+#endif
diff --git a/iocore/net/P_QUICNextProtocolAccept_native.h b/iocore/net/P_QUICNextProtocolAccept_native.h
new file mode 100644
index 0000000..6dee222
--- /dev/null
+++ b/iocore/net/P_QUICNextProtocolAccept_native.h
@@ -0,0 +1,59 @@
+/** @file
+
+  QUICNextProtocolAccept
+
+  @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.
+ */
+
+#pragma once
+
+#include "P_QUICNetVConnection.h"
+#include "P_SSLNextProtocolSet.h"
+#include "I_IOBuffer.h"
+
+class QUICNextProtocolAccept : public SessionAccept
+{
+public:
+  QUICNextProtocolAccept();
+  ~QUICNextProtocolAccept();
+
+  bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *) override;
+
+  // Register handler as an endpoint for the specified protocol. Neither
+  // handler nor protocol are copied, so the caller must guarantee their
+  // lifetime is at least as long as that of the acceptor.
+  bool registerEndpoint(const char *protocol, Continuation *handler);
+
+  void enableProtocols(const SessionProtocolSet &protos);
+
+  SLINK(QUICNextProtocolAccept, link);
+  SSLNextProtocolSet *getProtoSet();
+
+  // noncopyable
+  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;
+  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete;
+
+private:
+  int mainEvent(int event, void *netvc) override;
+
+  SSLNextProtocolSet protoset;
+  SessionProtocolSet protoenabled;
+
+  friend struct QUICNextProtocolTrampoline;
+};
diff --git a/iocore/net/P_QUICNextProtocolAccept_quiche.h b/iocore/net/P_QUICNextProtocolAccept_quiche.h
new file mode 100644
index 0000000..49654d5
--- /dev/null
+++ b/iocore/net/P_QUICNextProtocolAccept_quiche.h
@@ -0,0 +1,59 @@
+/** @file
+
+  QUICNextProtocolAccept
+
+  @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.
+ */
+
+#pragma once
+
+#include "P_QUICNetVConnection_quiche.h"
+#include "P_SSLNextProtocolSet.h"
+#include "I_IOBuffer.h"
+
+class QUICNextProtocolAccept : public SessionAccept
+{
+public:
+  QUICNextProtocolAccept();
+  ~QUICNextProtocolAccept();
+
+  bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *) override;
+
+  // Register handler as an endpoint for the specified protocol. Neither
+  // handler nor protocol are copied, so the caller must guarantee their
+  // lifetime is at least as long as that of the acceptor.
+  bool registerEndpoint(const char *protocol, Continuation *handler);
+
+  void enableProtocols(const SessionProtocolSet &protos);
+
+  SLINK(QUICNextProtocolAccept, link);
+  SSLNextProtocolSet *getProtoSet();
+
+  // noncopyable
+  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;
+  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete;
+
+private:
+  int mainEvent(int event, void *netvc) override;
+
+  SSLNextProtocolSet protoset;
+  SessionProtocolSet protoenabled;
+
+  friend struct QUICNextProtocolTrampoline;
+};
diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler.h
index 9428b89..4ec56a5 100644
--- a/iocore/net/P_QUICPacketHandler.h
+++ b/iocore/net/P_QUICPacketHandler.h
@@ -23,98 +23,10 @@
 
 #pragma once
 
-#include "tscore/ink_platform.h"
-#include "P_Connection.h"
-#include "P_NetAccept.h"
-#include "quic/QUICTypes.h"
-#include "quic/QUICConnectionTable.h"
-#include "quic/QUICResetTokenTable.h"
+#include "tscore/ink_config.h"
 
-class QUICClosedConCollector;
-class QUICNetVConnection;
-class QUICPacket;
-class QUICPacketHeaderProtector;
-
-class QUICPacketHandler
-{
-public:
-  QUICPacketHandler(QUICResetTokenTable &rtable);
-  ~QUICPacketHandler();
-
-  void send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &pn_protector);
-  void send_packet(QUICNetVConnection *vc, const Ptr<IOBufferBlock> &udp_payload);
-
-  void close_connection(QUICNetVConnection *conn);
-
-protected:
-  void _send_packet(const QUICPacket &packet, UDPConnection *udp_con, IpEndpoint &addr, uint32_t pmtu,
-                    const QUICPacketHeaderProtector *ph_protector, int dcil);
-  void _send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload);
-  QUICConnection *_check_stateless_reset(const uint8_t *buf, size_t buf_len);
-
-  // FIXME Remove this
-  // QUICPacketHandler could be a continuation, but NetAccept is a continuation too.
-  virtual Continuation *_get_continuation() = 0;
-
-  Event *_collector_event                       = nullptr;
-  QUICClosedConCollector *_closed_con_collector = nullptr;
-
-  virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
-
-  QUICResetTokenTable &_rtable;
-};
-
-/*
- * @class QUICPacketHandlerIn
- * @brief QUIC Packet Handler for incoming connections
- */
-class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
-{
-public:
-  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, QUICResetTokenTable &rtable);
-  ~QUICPacketHandlerIn();
-
-  // NetAccept
-  virtual NetProcessor *getNetProcessor() const override;
-  virtual NetAccept *clone() const override;
-  virtual int acceptEvent(int event, void *e) override;
-  void init_accept(EThread *t) override;
-
-protected:
-  // QUICPacketHandler
-  Continuation *_get_continuation() override;
-
-private:
-  void _recv_packet(int event, UDPPacket *udp_packet) override;
-  int _stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, QUICConnectionId dcid,
-                       QUICConnectionId scid, QUICConnectionId *original_cid, QUICConnectionId *retry_cid, QUICVersion version);
-  bool _send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
-                             size_t maximum_size);
-  void _send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len, UDPConnection *connection,
-                                 IpEndpoint from);
-
-  QUICConnectionTable &_ctable;
-};
-
-/*
- * @class QUICPacketHandlerOut
- * @brief QUIC Packet Handler for outgoing connections
- */
-class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
-{
-public:
-  QUICPacketHandlerOut(QUICResetTokenTable &rtable);
-  ~QUICPacketHandlerOut(){};
-
-  void init(QUICNetVConnection *vc);
-  int event_handler(int event, Event *data);
-
-protected:
-  // QUICPacketHandler
-  Continuation *_get_continuation() override;
-
-private:
-  void _recv_packet(int event, UDPPacket *udp_packet) override;
-
-  QUICNetVConnection *_vc = nullptr;
-};
+#if HAVE_QUICHE_H
+#include "P_QUICPacketHandler_quiche.h"
+#else
+#include "P_QUICPacketHandler_native.h"
+#endif
diff --git a/iocore/net/P_QUICPacketHandler_native.h b/iocore/net/P_QUICPacketHandler_native.h
new file mode 100644
index 0000000..9428b89
--- /dev/null
+++ b/iocore/net/P_QUICPacketHandler_native.h
@@ -0,0 +1,120 @@
+/** @file
+
+  A brief file description
+
+  @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.
+ */
+
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Connection.h"
+#include "P_NetAccept.h"
+#include "quic/QUICTypes.h"
+#include "quic/QUICConnectionTable.h"
+#include "quic/QUICResetTokenTable.h"
+
+class QUICClosedConCollector;
+class QUICNetVConnection;
+class QUICPacket;
+class QUICPacketHeaderProtector;
+
+class QUICPacketHandler
+{
+public:
+  QUICPacketHandler(QUICResetTokenTable &rtable);
+  ~QUICPacketHandler();
+
+  void send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &pn_protector);
+  void send_packet(QUICNetVConnection *vc, const Ptr<IOBufferBlock> &udp_payload);
+
+  void close_connection(QUICNetVConnection *conn);
+
+protected:
+  void _send_packet(const QUICPacket &packet, UDPConnection *udp_con, IpEndpoint &addr, uint32_t pmtu,
+                    const QUICPacketHeaderProtector *ph_protector, int dcil);
+  void _send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload);
+  QUICConnection *_check_stateless_reset(const uint8_t *buf, size_t buf_len);
+
+  // FIXME Remove this
+  // QUICPacketHandler could be a continuation, but NetAccept is a continuation too.
+  virtual Continuation *_get_continuation() = 0;
+
+  Event *_collector_event                       = nullptr;
+  QUICClosedConCollector *_closed_con_collector = nullptr;
+
+  virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
+
+  QUICResetTokenTable &_rtable;
+};
+
+/*
+ * @class QUICPacketHandlerIn
+ * @brief QUIC Packet Handler for incoming connections
+ */
+class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
+{
+public:
+  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, QUICResetTokenTable &rtable);
+  ~QUICPacketHandlerIn();
+
+  // NetAccept
+  virtual NetProcessor *getNetProcessor() const override;
+  virtual NetAccept *clone() const override;
+  virtual int acceptEvent(int event, void *e) override;
+  void init_accept(EThread *t) override;
+
+protected:
+  // QUICPacketHandler
+  Continuation *_get_continuation() override;
+
+private:
+  void _recv_packet(int event, UDPPacket *udp_packet) override;
+  int _stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, QUICConnectionId dcid,
+                       QUICConnectionId scid, QUICConnectionId *original_cid, QUICConnectionId *retry_cid, QUICVersion version);
+  bool _send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
+                             size_t maximum_size);
+  void _send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len, UDPConnection *connection,
+                                 IpEndpoint from);
+
+  QUICConnectionTable &_ctable;
+};
+
+/*
+ * @class QUICPacketHandlerOut
+ * @brief QUIC Packet Handler for outgoing connections
+ */
+class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
+{
+public:
+  QUICPacketHandlerOut(QUICResetTokenTable &rtable);
+  ~QUICPacketHandlerOut(){};
+
+  void init(QUICNetVConnection *vc);
+  int event_handler(int event, Event *data);
+
+protected:
+  // QUICPacketHandler
+  Continuation *_get_continuation() override;
+
+private:
+  void _recv_packet(int event, UDPPacket *udp_packet) override;
+
+  QUICNetVConnection *_vc = nullptr;
+};
diff --git a/iocore/net/P_QUICPacketHandler_quiche.h b/iocore/net/P_QUICPacketHandler_quiche.h
new file mode 100644
index 0000000..d6b9194
--- /dev/null
+++ b/iocore/net/P_QUICPacketHandler_quiche.h
@@ -0,0 +1,90 @@
+/** @file
+
+  A brief file description
+
+  @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.
+ */
+
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Connection.h"
+#include "P_NetAccept.h"
+#include <quiche.h>
+
+class QUICNetVConnection;
+class QUICConnectionTable;
+class QUICClosedConCollector;
+
+class QUICPacketHandler
+{
+public:
+  QUICPacketHandler();
+  virtual ~QUICPacketHandler();
+
+  void send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload, uint16_t segment_size = 0);
+  void close_connection(QUICNetVConnection *conn);
+
+protected:
+  Event *_collector_event                       = nullptr;
+  QUICClosedConCollector *_closed_con_collector = nullptr;
+
+  virtual Continuation *_get_continuation() = 0;
+
+  virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
+};
+
+class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
+{
+public:
+  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, quiche_config &config);
+  ~QUICPacketHandlerIn();
+
+  // NetAccept
+  virtual NetProcessor *getNetProcessor() const override;
+  virtual NetAccept *clone() const override;
+  virtual int acceptEvent(int event, void *e) override;
+  void init_accept(EThread *t) override;
+
+protected:
+  // QUICPacketHandler
+  Continuation *_get_continuation() override;
+
+private:
+  QUICConnectionTable &_ctable;
+  quiche_config &_quiche_config;
+
+  void _recv_packet(int event, UDPPacket *udpPacket) override;
+};
+
+class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
+{
+public:
+  QUICPacketHandlerOut(){};
+  ~QUICPacketHandlerOut(){};
+
+  void init(QUICNetVConnection *vc);
+
+protected:
+  // QUICPacketHandler
+  Continuation *_get_continuation() override;
+
+private:
+  void _recv_packet(int event, UDPPacket *udp_packet) override;
+};
diff --git a/iocore/net/P_SSLCertLookup.h b/iocore/net/P_SSLCertLookup.h
index a36aba9..e8ddf62 100644
--- a/iocore/net/P_SSLCertLookup.h
+++ b/iocore/net/P_SSLCertLookup.h
@@ -25,6 +25,8 @@
 
 #include <set>
 #include <openssl/ssl.h>
+#include <mutex>
+#include <unordered_map>
 
 #include "ProxyConfig.h"
 
diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h
index d383e0e..3b4ffcc 100644
--- a/iocore/net/P_SSLConfig.h
+++ b/iocore/net/P_SSLConfig.h
@@ -101,6 +101,9 @@
   long ssl_ctx_options;
   long ssl_client_ctx_options;
 
+  unsigned char alpn_protocols_array[MAX_ALPN_STRING];
+  int alpn_protocols_array_size = 0;
+
   char *server_tls13_cipher_suites;
   char *client_tls13_cipher_suites;
   char *server_groups_list;
diff --git a/iocore/net/P_UDPConnection.h b/iocore/net/P_UDPConnection.h
index 10e57c2..95aedbc 100644
--- a/iocore/net/P_UDPConnection.h
+++ b/iocore/net/P_UDPConnection.h
@@ -30,6 +30,7 @@
  ****************************************************************************/
 #pragma once
 
+#include "tscore/ink_atomic.h"
 #include "I_UDPNet.h"
 
 class UDPConnectionInternal : public UDPConnection
diff --git a/iocore/net/P_UDPNet.h b/iocore/net/P_UDPNet.h
index a7de964..8df8628 100644
--- a/iocore/net/P_UDPNet.h
+++ b/iocore/net/P_UDPNet.h
@@ -40,9 +40,14 @@
 #include "P_UnixUDPConnection.h"
 #include "P_UDPIOEvent.h"
 
+#include "netinet/udp.h"
+
 class UDPNetHandler;
 
-struct UDPNetProcessorInternal : public UDPNetProcessor {
+class UDPNetProcessorInternal : public UDPNetProcessor
+{
+public:
+  EventType register_event_type() override;
   int start(int n_udp_threads, size_t stacksize) override;
   void udp_read_from_net(UDPNetHandler *nh, UDPConnection *uc);
   int udp_callback(UDPNetHandler *nh, UDPConnection *uc, EThread *thread);
@@ -278,6 +283,9 @@
   ink_hrtime last_service = 0;
   int packets             = 0;
   int added               = 0;
+#ifdef SOL_UDP
+  bool use_udp_gso = false;
+#endif
 
 public:
   // Outgoing UDP Packet Queue
@@ -286,12 +294,13 @@
   void service(UDPNetHandler *);
 
   void SendPackets();
-  void SendUDPPacket(UDPPacketInternal *p, int32_t pktLen);
+  void SendUDPPacket(UDPPacketInternal *p);
+  int SendMultipleUDPPackets(UDPPacketInternal **p, uint16_t n);
 
   // Interface exported to the outside world
   void send(UDPPacket *p);
 
-  UDPQueue();
+  UDPQueue(bool enable_gso);
   ~UDPQueue();
 };
 
@@ -301,7 +310,7 @@
 {
 public:
   // engine for outgoing packets
-  UDPQueue udpOutQueue{};
+  UDPQueue udpOutQueue;
 
   // New UDPConnections
   // to hold the newly created descriptors before scheduling them on the servicing buckets.
@@ -323,7 +332,7 @@
   int waitForActivity(ink_hrtime timeout) override;
   void signalActivity() override;
 
-  UDPNetHandler();
+  UDPNetHandler(bool enable_gso);
 };
 
 struct PollCont;
diff --git a/iocore/net/P_UDPPacket.h b/iocore/net/P_UDPPacket.h
index 1400373..22b432f 100644
--- a/iocore/net/P_UDPPacket.h
+++ b/iocore/net/P_UDPPacket.h
@@ -42,7 +42,8 @@
 
   SLINK(UDPPacketInternal, alink); // atomic link
   // packet scheduling stuff: keep it a doubly linked list
-  uint64_t pktLength = 0;
+  uint64_t pktLength    = 0;
+  uint16_t segment_size = 0;
 
   int reqGenerationNum     = 0;
   ink_hrtime delivery_time = 0; // when to deliver packet
@@ -162,7 +163,7 @@
 }
 
 TS_INLINE UDPPacket *
-new_UDPPacket(struct sockaddr const *to, ink_hrtime when, Ptr<IOBufferBlock> &buf)
+new_UDPPacket(struct sockaddr const *to, ink_hrtime when, Ptr<IOBufferBlock> &buf, uint16_t segment_size = 0)
 {
   UDPPacketInternal *p = udpPacketAllocator.alloc();
 
@@ -171,7 +172,8 @@
   p->delivery_time         = when;
   if (to)
     ats_ip_copy(&p->to, to);
-  p->chain = buf;
+  p->chain        = buf;
+  p->segment_size = segment_size;
   return p;
 }
 
diff --git a/iocore/net/P_UnixNet.h b/iocore/net/P_UnixNet.h
index 66a0297..7c1f3d1 100644
--- a/iocore/net/P_UnixNet.h
+++ b/iocore/net/P_UnixNet.h
@@ -436,7 +436,7 @@
   ink_hrtime t = Thread::get_hrtime();
   if (t - last_shedding_warning > NET_THROTTLE_MESSAGE_EVERY) {
     last_shedding_warning = t;
-    RecSignalWarning(REC_SIGNAL_SYSTEM_ERROR, "number of connections reaching shedding limit");
+    Warning("number of connections reaching shedding limit");
   }
 }
 
@@ -458,9 +458,8 @@
   if (t - last_throttle_warning > NET_THROTTLE_MESSAGE_EVERY) {
     last_throttle_warning = t;
     int connections       = net_connections_to_throttle(type);
-    RecSignalWarning(REC_SIGNAL_SYSTEM_ERROR,
-                     "too many connections, throttling.  connection_type=%s, current_connections=%d, net_connections_throttle=%d",
-                     type == ACCEPT ? "ACCEPT" : "CONNECT", connections, net_connections_throttle);
+    Warning("too many connections, throttling.  connection_type=%s, current_connections=%d, net_connections_throttle=%d",
+            type == ACCEPT ? "ACCEPT" : "CONNECT", connections, net_connections_throttle);
   }
 }
 
diff --git a/iocore/net/QUICNet.cc b/iocore/net/QUICNet.cc
index 31d33d9..da073dd 100644
--- a/iocore/net/QUICNet.cc
+++ b/iocore/net/QUICNet.cc
@@ -60,6 +60,26 @@
 
 QUICPollCont::~QUICPollCont() {}
 
+#if HAVE_QUICHE_H
+void
+QUICPollCont::_process_packet(QUICPollEvent *e, NetHandler *nh)
+{
+  UDPPacketInternal *p   = e->packet;
+  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(e->con);
+
+  vc->read.triggered = 1;
+  vc->handle_received_packet(p);
+
+  // Push QUICNetVC into nethandler's enabled list
+  int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1);
+  if (!isin) {
+    nh->read_enable_list.push(vc);
+  }
+
+  // Note: We should free QUICPollEvent here since vc could be freed from other thread.
+  e->free();
+}
+#else
 void
 QUICPollCont::_process_long_header_packet(QUICPollEvent *e, NetHandler *nh)
 {
@@ -118,6 +138,7 @@
   // Note: We should free QUICPollEvent here since vc could be freed from other thread.
   e->free();
 }
+#endif
 
 //
 // QUICPollCont continuation which traverse the inQueue(ASLL)
@@ -128,7 +149,6 @@
 QUICPollCont::pollEvent(int, Event *)
 {
   ink_assert(this->mutex->thread_holding == this_thread());
-  uint8_t *buf;
   QUICPollEvent *e;
   NetHandler *nh = get_NetHandler(this->mutex->thread_holding);
 
@@ -147,6 +167,10 @@
   }
 
   while ((e = result.pop())) {
+#if HAVE_QUICHE_H
+    this->_process_packet(e, nh);
+#else
+    uint8_t *buf;
     buf = reinterpret_cast<uint8_t *>(e->packet->getIOBlockChain()->buf());
     if (QUICInvariants::is_long_header(buf)) {
       // Long Header Packet with Connection ID, has a valid type value.
@@ -155,6 +179,7 @@
       // Short Header Packet with Connection ID, has a valid type value.
       this->_process_short_header_packet(e, nh);
     }
+#endif
   }
 
   return EVENT_CONT;
diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor.cc
index a2af493..c5b7822 100644
--- a/iocore/net/QUICNetProcessor.cc
+++ b/iocore/net/QUICNetProcessor.cc
@@ -135,7 +135,7 @@
   if (opt->local_ip.isValid()) {
     con->setBinding(opt->local_ip, opt->local_port);
   }
-  con->bindToThread(packet_handler);
+  con->bindToThread(packet_handler, t);
 
   PollCont *pc       = get_UDPPollCont(con->ethread);
   PollDescriptor *pd = pc->pollDescriptor;
@@ -220,8 +220,5 @@
   na->action_->server = &na->server;
   na->init_accept();
 
-  SCOPED_MUTEX_LOCK(lock, na->mutex, this_ethread());
-  udpNet.UDPBind((Continuation *)na, &na->server.accept_addr.sa, fd, 1048576, 1048576);
-
   return na->action_.get();
 }
diff --git a/iocore/net/QUICNetProcessor_quiche.cc b/iocore/net/QUICNetProcessor_quiche.cc
new file mode 100644
index 0000000..2320b5a
--- /dev/null
+++ b/iocore/net/QUICNetProcessor_quiche.cc
@@ -0,0 +1,255 @@
+/** @file
+
+  @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 "tscore/ink_config.h"
+#include "tscore/I_Layout.h"
+
+#include "P_Net.h"
+#include "P_QUICNet.h"
+#include "records/I_RecHttp.h"
+
+#include "P_QUICNetProcessor_quiche.h"
+#include "P_QUICPacketHandler_quiche.h"
+#include "P_QUICNetVConnection_quiche.h"
+#include "QUICGlobals.h"
+#include "QUICTypes.h"
+#include "QUICConfig.h"
+#include "QUICMultiCertConfigLoader.h"
+
+#include <quiche.h>
+
+//
+// Global Data
+//
+
+QUICNetProcessor quic_NetProcessor;
+
+static void
+debug_log(const char *line, void *argp)
+{
+  Debug("vv_quiche", "%s\n", line);
+}
+
+QUICNetProcessor::QUICNetProcessor() {}
+
+QUICNetProcessor::~QUICNetProcessor()
+{
+  if (this->_quiche_config != nullptr) {
+    quiche_config_free(this->_quiche_config);
+  }
+}
+
+void
+QUICNetProcessor::init()
+{
+  // first we allocate a QUICPollCont.
+  this->quicPollCont_offset = eventProcessor.allocate(sizeof(QUICPollCont));
+
+  // schedule event
+  eventProcessor.schedule_spawn(&initialize_thread_for_quic_net, ET_NET);
+}
+
+int
+QUICNetProcessor::start(int, size_t stacksize)
+{
+  QUIC::init();
+  // This initialization order matters ...
+  // QUICInitializeLibrary();
+  QUICConfig::startup();
+  QUICCertConfig::startup();
+  QUICCertConfig::scoped_config certs;
+  SSLCertContext *context = certs->get(0);
+
+  quiche_enable_debug_logging(debug_log, NULL);
+  this->_quiche_config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+  quiche_config_set_application_protos(this->_quiche_config, (uint8_t *)"\02h3\x05h3-29\x05hq-29\x05h3-27\x05hq-27", 27);
+  if (context->userconfig->cert != nullptr) {
+    quiche_config_load_cert_chain_from_pem_file(this->_quiche_config, context->userconfig->cert);
+  }
+  if (context->userconfig->key != nullptr) {
+    quiche_config_load_priv_key_from_pem_file(this->_quiche_config, context->userconfig->key);
+  }
+
+  quiche_config_set_max_idle_timeout(this->_quiche_config, 5000);
+  quiche_config_set_max_recv_udp_payload_size(this->_quiche_config, 16384);
+  quiche_config_set_max_send_udp_payload_size(this->_quiche_config, 16384);
+  quiche_config_set_initial_max_data(this->_quiche_config, 10000000);
+  quiche_config_set_initial_max_stream_data_bidi_local(this->_quiche_config, 1000000);
+  quiche_config_set_initial_max_stream_data_bidi_remote(this->_quiche_config, 1000000);
+  quiche_config_set_initial_max_stream_data_uni(this->_quiche_config, 1000000);
+  quiche_config_set_initial_max_streams_bidi(this->_quiche_config, 100);
+  quiche_config_set_initial_max_streams_uni(this->_quiche_config, 100);
+  quiche_config_set_cc_algorithm(this->_quiche_config, QUICHE_CC_RENO);
+
+#ifdef TLS1_3_VERSION_DRAFT_TXT
+  // FIXME: remove this when TLS1_3_VERSION_DRAFT_TXT is removed
+  Debug("quic_ps", "%s", TLS1_3_VERSION_DRAFT_TXT);
+#endif
+
+  return 0;
+}
+
+NetAccept *
+QUICNetProcessor::createNetAccept(const NetProcessor::AcceptOptions &opt)
+{
+  if (this->_ctable == nullptr) {
+    QUICConfig::scoped_config params;
+    this->_ctable = new QUICConnectionTable(params->connection_table_size());
+  }
+  return (NetAccept *)new QUICPacketHandlerIn(opt, *this->_ctable, *this->_quiche_config);
+}
+
+NetVConnection *
+QUICNetProcessor::allocate_vc(EThread *t)
+{
+  QUICNetVConnection *vc;
+
+  if (t) {
+    vc = THREAD_ALLOC(quicNetVCAllocator, t);
+    new (vc) QUICNetVConnection();
+  } else {
+    if (likely(vc = quicNetVCAllocator.alloc())) {
+      new (vc) QUICNetVConnection();
+      vc->from_accept_thread = true;
+    }
+  }
+  vc->ep.syscall = false;
+  return vc;
+}
+
+Action *
+QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, NetVCOptions *opt)
+{
+  Debug("quic_ps", "connect to server");
+  EThread *t = cont->mutex->thread_holding;
+  ink_assert(t);
+
+  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(this->allocate_vc(t));
+
+  if (opt) {
+    vc->options = *opt;
+  } else {
+    opt = &vc->options;
+  }
+
+  int fd;
+  Action *status;
+  bool result = udpNet.CreateUDPSocket(&fd, remote_addr, &status, *opt);
+  if (!result) {
+    vc->free(t);
+    return status;
+  }
+
+  // Setup UDPConnection
+  UnixUDPConnection *con = new UnixUDPConnection(fd);
+  Debug("quic_ps", "con=%p fd=%d", con, fd);
+
+  QUICPacketHandlerOut *packet_handler = new QUICPacketHandlerOut();
+  if (opt->local_ip.isValid()) {
+    con->setBinding(opt->local_ip, opt->local_port);
+  }
+  con->bindToThread(packet_handler, t);
+
+  PollCont *pc       = get_UDPPollCont(con->ethread);
+  PollDescriptor *pd = pc->pollDescriptor;
+
+  errno   = 0;
+  int res = con->ep.start(pd, con, EVENTIO_READ);
+  if (res < 0) {
+    Debug("udpnet", "Error: %s (%d)", strerror(errno), errno);
+  }
+
+  // Setup QUICNetVConnection
+  QUICConnectionId client_dst_cid;
+  client_dst_cid.randomize();
+  // vc->init set handler of vc `QUICNetVConnection::startEvent`
+  vc->init(QUIC_SUPPORTED_VERSIONS[0], client_dst_cid, client_dst_cid, con, packet_handler);
+  packet_handler->init(vc);
+
+  // Connection ID will be changed
+  vc->id = net_next_connection_number();
+  vc->set_context(NET_VCONNECTION_OUT);
+  vc->con.setRemote(remote_addr);
+  vc->submit_time = Thread::get_hrtime();
+  vc->mutex       = cont->mutex;
+  vc->action_     = cont;
+
+  if (t->is_event_type(opt->etype)) {
+    MUTEX_TRY_LOCK(lock, cont->mutex, t);
+    if (lock.is_locked()) {
+      MUTEX_TRY_LOCK(lock2, get_NetHandler(t)->mutex, t);
+      if (lock2.is_locked()) {
+        vc->connectUp(t, NO_FD);
+        return ACTION_RESULT_DONE;
+      }
+    }
+  }
+
+  // Try to stay on the current thread if it is the right type
+  if (t->is_event_type(opt->etype)) {
+    t->schedule_imm(vc);
+  } else { // Otherwise, pass along to another thread of the right type
+    eventProcessor.schedule_imm(vc, opt->etype);
+  }
+
+  return ACTION_RESULT_DONE;
+}
+
+Action *
+QUICNetProcessor::main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt)
+{
+  // UnixNetProcessor *this_unp = static_cast<UnixNetProcessor *>(this);
+  Debug("iocore_net_processor", "NetProcessor::main_accept - port %d,recv_bufsize %d, send_bufsize %d, sockopt 0x%0x",
+        opt.local_port, opt.recv_bufsize, opt.send_bufsize, opt.sockopt_flags);
+
+  ProxyMutex *mutex  = this_ethread()->mutex.get();
+  int accept_threads = opt.accept_threads; // might be changed.
+  IpEndpoint accept_ip;                    // local binding address.
+  // char thr_name[MAX_THREAD_NAME_LENGTH];
+
+  NetAccept *na = createNetAccept(opt);
+
+  if (accept_threads < 0) {
+    REC_ReadConfigInteger(accept_threads, "proxy.config.accept_threads");
+  }
+  NET_INCREMENT_DYN_STAT(net_accepts_currently_open_stat);
+
+  if (opt.localhost_only) {
+    accept_ip.setToLoopback(opt.ip_family);
+  } else if (opt.local_ip.isValid()) {
+    accept_ip.assign(opt.local_ip);
+  } else {
+    accept_ip.setToAnyAddr(opt.ip_family);
+  }
+  ink_assert(0 < opt.local_port && opt.local_port < 65536);
+  accept_ip.network_order_port() = htons(opt.local_port);
+
+  na->accept_fn = net_accept;
+  na->server.fd = fd;
+  ats_ip_copy(&na->server.accept_addr, &accept_ip);
+
+  na->action_         = new NetAcceptAction();
+  *na->action_        = cont;
+  na->action_->server = &na->server;
+  na->init_accept();
+
+  return na->action_.get();
+}
diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc
index 684ea60..84712aa 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -449,7 +449,7 @@
 
   this->_remote_flow_controller = new QUICRemoteConnectionFlowController(UINT64_MAX);
   this->_local_flow_controller  = new QUICLocalConnectionFlowController(&this->_rtt_measure, UINT64_MAX);
-  this->_stream_manager         = new QUICStreamManager(this->_context.get(), this->_application_map);
+  this->_stream_manager         = new QUICStreamManagerImpl(this->_context.get(), this->_application_map);
   this->_token_creator          = new QUICTokenCreator(this->_context.get());
 
   static constexpr int QUIC_STREAM_MANAGER_WEIGHT = QUICFrameGeneratorWeight::AFTER_DATA - 1;
@@ -1674,6 +1674,8 @@
       frame =
         g->generate_frame(frame_instance_buffer, level, this->_remote_flow_controller->credit(), max_frame_size, len, seq_num);
       if (frame) {
+        ink_release_assert(dynamic_cast<QUICStreamBase *>(frame->generated_by()) == nullptr);
+        ink_release_assert(dynamic_cast<QUICBidirectionalStream *>(frame->generated_by()) == nullptr);
         this->_context->trigger(QUICContext::CallbackEvent::FRAME_PACKETIZE, *frame);
         // Some frame types must not be sent on Initial and Handshake packets
         switch (auto t = frame->type(); level) {
diff --git a/iocore/net/QUICNetVConnection_quiche.cc b/iocore/net/QUICNetVConnection_quiche.cc
new file mode 100644
index 0000000..6f9edb8
--- /dev/null
+++ b/iocore/net/QUICNetVConnection_quiche.cc
@@ -0,0 +1,639 @@
+/** @file
+
+  A brief file description
+
+  @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 "P_QUICNetVConnection_quiche.h"
+#include "P_QUICPacketHandler_quiche.h"
+#include "quic/QUICStream_quiche.h"
+#include <quiche.h>
+
+static constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2);
+
+#define QUICConDebug(fmt, ...) Debug("quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
+#define QUICConVDebug(fmt, ...) Debug("v_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
+
+ClassAllocator<QUICNetVConnection> quicNetVCAllocator("quicNetVCAllocator");
+
+QUICNetVConnection::QUICNetVConnection() {}
+
+QUICNetVConnection::~QUICNetVConnection() {}
+
+void
+QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *,
+                         QUICPacketHandler *)
+{
+}
+
+void
+QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+                         QUICConnectionId retry_cid, UDPConnection *udp_con, quiche_conn *quiche_con,
+                         QUICPacketHandler *packet_handler, QUICConnectionTable *ctable)
+{
+  SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
+  this->_udp_con                     = udp_con;
+  this->_quiche_con                  = quiche_con;
+  this->_packet_handler              = packet_handler;
+  this->_original_quic_connection_id = original_cid;
+  this->_quic_connection_id.randomize();
+  this->_initial_source_connection_id = this->_quic_connection_id;
+
+  if (ctable) {
+    this->_ctable = ctable;
+    this->_ctable->insert(this->_quic_connection_id, this);
+    this->_ctable->insert(this->_original_quic_connection_id, this);
+  }
+}
+
+void
+QUICNetVConnection::free()
+{
+  this->free(this_ethread());
+}
+
+// called by ET_UDP
+void
+QUICNetVConnection::remove_connection_ids()
+{
+}
+
+// called by ET_UDP
+void
+QUICNetVConnection::destroy(EThread *t)
+{
+  QUICConDebug("Destroy connection");
+  if (from_accept_thread) {
+    quicNetVCAllocator.free(this);
+  } else {
+    THREAD_FREE(this, quicNetVCAllocator, t);
+  }
+}
+
+void
+QUICNetVConnection::set_local_addr()
+{
+}
+
+void
+QUICNetVConnection::free(EThread *t)
+{
+  QUICConDebug("Free connection");
+
+  this->_udp_con = nullptr;
+
+  quiche_conn_free(this->_quiche_con);
+
+  delete this->_application_map;
+  this->_application_map = nullptr;
+  delete this->_stream_manager;
+  this->_stream_manager = nullptr;
+
+  super::clear();
+  this->_context->trigger(QUICContext::CallbackEvent::CONNECTION_CLOSE);
+  ALPNSupport::clear();
+  TLSBasicSupport::clear();
+
+  this->_packet_handler->close_connection(this);
+  this->_packet_handler = nullptr;
+}
+
+void
+QUICNetVConnection::reenable(VIO *vio)
+{
+}
+
+int
+QUICNetVConnection::state_handshake(int event, Event *data)
+{
+  if (quiche_conn_is_established(this->_quiche_con)) {
+    this->_switch_to_established_state();
+    return this->handleEvent(event, data);
+  }
+
+  switch (event) {
+  case QUIC_EVENT_PACKET_READ_READY:
+    this->_handle_read_ready();
+    break;
+  case QUIC_EVENT_PACKET_WRITE_READY:
+    this->_close_packet_write_ready(data);
+    this->_handle_write_ready();
+    // Reschedule WRITE_READY
+    this->_schedule_packet_write_ready(true);
+    break;
+  case EVENT_INTERVAL:
+    this->_handle_interval();
+    break;
+  case VC_EVENT_EOS:
+  case VC_EVENT_ERROR:
+  case VC_EVENT_ACTIVE_TIMEOUT:
+  case VC_EVENT_INACTIVITY_TIMEOUT:
+    _unschedule_packet_write_ready();
+    this->closed = 1;
+    break;
+  default:
+    QUICConDebug("Unhandleed event: %d", event);
+    break;
+  }
+
+  return EVENT_DONE;
+}
+
+int
+QUICNetVConnection::state_established(int event, Event *data)
+{
+  switch (event) {
+  case QUIC_EVENT_PACKET_READ_READY:
+    this->_handle_read_ready();
+    break;
+  case QUIC_EVENT_PACKET_WRITE_READY:
+    this->_close_packet_write_ready(data);
+    this->_handle_write_ready();
+    // Reschedule WRITE_READY
+    this->_schedule_packet_write_ready(true);
+    break;
+  case EVENT_INTERVAL:
+    this->_handle_interval();
+    break;
+  case VC_EVENT_EOS:
+  case VC_EVENT_ERROR:
+  case VC_EVENT_ACTIVE_TIMEOUT:
+  case VC_EVENT_INACTIVITY_TIMEOUT:
+    _unschedule_packet_write_ready();
+    this->closed = 1;
+    break;
+  default:
+    QUICConDebug("Unhandleed event: %d", event);
+    break;
+  }
+  return EVENT_DONE;
+}
+
+void
+QUICNetVConnection::_switch_to_established_state()
+{
+  QUICConDebug("Enter state_connection_established");
+  this->_record_tls_handshake_end_time();
+  SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_established);
+  this->_start_application();
+  this->_handshake_completed = true;
+}
+
+void
+QUICNetVConnection::_start_application()
+{
+  if (!this->_application_started) {
+    this->_application_started = true;
+
+    const uint8_t *app_name;
+    size_t app_name_len = 0;
+    quiche_conn_application_proto(this->_quiche_con, &app_name, &app_name_len);
+    if (app_name == nullptr) {
+      app_name     = reinterpret_cast<const uint8_t *>(IP_PROTO_TAG_HTTP_QUIC.data());
+      app_name_len = IP_PROTO_TAG_HTTP_QUIC.size();
+    }
+
+    this->set_negotiated_protocol_id({reinterpret_cast<const char *>(app_name), static_cast<size_t>(app_name_len)});
+
+    if (netvc_context == NET_VCONNECTION_IN) {
+      if (!this->setSelectedProtocol(app_name, app_name_len)) {
+        // this->_handle_error(std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION));
+      } else {
+        this->endpoint()->handleEvent(NET_EVENT_ACCEPT, this);
+      }
+    } else {
+      this->action_.continuation->handleEvent(NET_EVENT_OPEN, this);
+    }
+  }
+}
+
+bool
+QUICNetVConnection::shouldDestroy()
+{
+  return this->refcount() == 0;
+}
+
+VIO *
+QUICNetVConnection::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf)
+{
+  ink_assert(false);
+  return nullptr;
+}
+
+VIO *
+QUICNetVConnection::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner)
+{
+  ink_assert(false);
+  return nullptr;
+}
+
+int
+QUICNetVConnection::acceptEvent(int event, Event *e)
+{
+  EThread *t    = (e == nullptr) ? this_ethread() : e->ethread;
+  NetHandler *h = get_NetHandler(t);
+
+  MUTEX_TRY_LOCK(lock, h->mutex, t);
+  if (!lock.is_locked()) {
+    if (event == EVENT_NONE) {
+      t->schedule_in(this, HRTIME_MSECONDS(net_retry_delay));
+      return EVENT_DONE;
+    } else {
+      e->schedule_in(HRTIME_MSECONDS(net_retry_delay));
+      return EVENT_CONT;
+    }
+  }
+
+  this->_context         = std::make_unique<QUICContext>(this);
+  this->_application_map = new QUICApplicationMap();
+  this->_stream_manager  = new QUICStreamManagerImpl(this->_context.get(), this->_application_map);
+
+  // this->thread is already assigned by QUICPacketHandlerIn::_recv_packet
+  ink_assert(this->thread == this_ethread());
+
+  // Send this NetVC to NetHandler and start to polling read & write event.
+  if (h->startIO(this) < 0) {
+    free(t);
+    return EVENT_DONE;
+  }
+
+  // FIXME: complete do_io_xxxx instead
+  this->read.enabled = 1;
+
+  // Handshake callback handler.
+  SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_handshake);
+
+  // Send this netvc to InactivityCop.
+  nh->startCop(this);
+
+  if (inactivity_timeout_in) {
+    set_inactivity_timeout(inactivity_timeout_in);
+  } else {
+    set_inactivity_timeout(0);
+  }
+
+  if (active_timeout_in) {
+    set_active_timeout(active_timeout_in);
+  }
+
+  action_.continuation->handleEvent(NET_EVENT_ACCEPT, this);
+  this->_schedule_packet_write_ready();
+
+  this->thread->schedule_in(this, HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
+
+  return EVENT_DONE;
+}
+
+int
+QUICNetVConnection::connectUp(EThread *t, int fd)
+{
+  return 0;
+}
+
+QUICStreamManager *
+QUICNetVConnection::stream_manager()
+{
+  return this->_stream_manager;
+}
+
+void
+QUICNetVConnection::close_quic_connection(QUICConnectionErrorUPtr error)
+{
+}
+
+void
+QUICNetVConnection::reset_quic_connection()
+{
+}
+
+void
+QUICNetVConnection::handle_received_packet(UDPPacket *packet)
+{
+  IOBufferBlock *block = packet->getIOBlockChain();
+  uint8_t *buf         = reinterpret_cast<uint8_t *>(block->buf());
+  uint64_t buf_len     = block->size();
+
+  net_activity(this, this_ethread());
+  quiche_recv_info recv_info = {
+    &packet->from.sa,
+    static_cast<socklen_t>(packet->from.isIp4() ? sizeof(packet->from.sin) : sizeof(packet->from.sin6)),
+#ifdef HAVE_QUICHE_CONFIG_SET_ACTIVE_CONNECTION_ID_LIMIT
+    &packet->to.sa,
+    static_cast<socklen_t>(packet->to.isIp4() ? sizeof(packet->to.sin) : sizeof(packet->to.sin6)),
+#endif
+  };
+
+  ssize_t done = quiche_conn_recv(this->_quiche_con, buf, buf_len, &recv_info);
+  if (done < 0) {
+    QUICConVDebug("failed to process packet: %zd", done);
+    return;
+  }
+}
+
+void
+QUICNetVConnection::ping()
+{
+}
+
+QUICConnectionId
+QUICNetVConnection::peer_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::original_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::first_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::retry_source_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::initial_source_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::connection_id() const
+{
+  return {};
+}
+
+std::string_view
+QUICNetVConnection::cids() const
+{
+  return "";
+}
+
+const QUICFiveTuple
+QUICNetVConnection::five_tuple() const
+{
+  return {};
+}
+
+uint32_t
+QUICNetVConnection::pmtu() const
+{
+  return 0;
+}
+
+NetVConnectionContext_t
+QUICNetVConnection::direction() const
+{
+  return NET_VCONNECTION_IN;
+}
+
+QUICVersion
+QUICNetVConnection::negotiated_version() const
+{
+  return 0;
+}
+
+std::string_view
+QUICNetVConnection::negotiated_application_name() const
+{
+  const uint8_t *name;
+  size_t name_len = 0;
+  quiche_conn_application_proto(this->_quiche_con, &name, &name_len);
+
+  return std::string_view(reinterpret_cast<const char *>(name), name_len);
+}
+
+bool
+QUICNetVConnection::is_closed() const
+{
+  return quiche_conn_is_closed(this->_quiche_con);
+}
+
+bool
+QUICNetVConnection::is_at_anti_amplification_limit() const
+{
+  return false;
+}
+
+bool
+QUICNetVConnection::is_address_validation_completed() const
+{
+  return false;
+}
+
+bool
+QUICNetVConnection::is_handshake_completed() const
+{
+  return false;
+}
+
+bool
+QUICNetVConnection::has_keys_for(QUICPacketNumberSpace space) const
+{
+  return false;
+}
+
+std::vector<QUICFrameType>
+QUICNetVConnection::interests()
+{
+  return {};
+}
+
+QUICConnectionErrorUPtr
+QUICNetVConnection::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
+{
+  return nullptr;
+}
+
+void
+QUICNetVConnection::net_read_io(NetHandler *nh, EThread *lthread)
+{
+  if (quiche_conn_is_readable(this->_quiche_con)) {
+    SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+    this->handleEvent(QUIC_EVENT_PACKET_READ_READY, nullptr);
+  }
+}
+
+int64_t
+QUICNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs)
+{
+  return 0;
+}
+
+void
+QUICNetVConnection::_schedule_packet_write_ready(bool delay)
+{
+  if (!this->_packet_write_ready) {
+    if (delay) {
+      this->_packet_write_ready = this->thread->schedule_in(this, WRITE_READY_INTERVAL, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
+    } else {
+      this->_packet_write_ready = this->thread->schedule_imm(this, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
+    }
+  }
+}
+
+void
+QUICNetVConnection::_unschedule_packet_write_ready()
+{
+  if (this->_packet_write_ready) {
+    this->_packet_write_ready->cancel();
+    this->_packet_write_ready = nullptr;
+  }
+}
+
+void
+QUICNetVConnection::_close_packet_write_ready(Event *data)
+{
+  ink_assert(this->_packet_write_ready == data);
+  this->_packet_write_ready = nullptr;
+}
+
+void
+QUICNetVConnection::_handle_read_ready()
+{
+  quiche_stream_iter *readable = quiche_conn_readable(this->_quiche_con);
+  uint64_t s                   = 0;
+  while (quiche_stream_iter_next(readable, &s)) {
+    QUICStreamImpl *stream;
+    QUICConVDebug("stream %" PRIu64 " is readable\n", s);
+    stream = static_cast<QUICStreamImpl *>(quiche_conn_stream_application_data(this->_quiche_con, s));
+    if (stream == nullptr) {
+      this->_stream_manager->create_stream(s);
+      stream = static_cast<QUICStreamImpl *>(this->_stream_manager->find_stream(s));
+      quiche_conn_stream_init_application_data(this->_quiche_con, s, stream);
+    }
+    stream->receive_data(this->_quiche_con);
+  }
+  quiche_stream_iter_free(readable);
+}
+
+void
+QUICNetVConnection::_handle_write_ready()
+{
+  if (quiche_conn_is_established(this->_quiche_con)) {
+    quiche_stream_iter *writable = quiche_conn_writable(this->_quiche_con);
+    uint64_t s                   = 0;
+    while (quiche_stream_iter_next(writable, &s)) {
+      QUICStreamImpl *stream;
+      stream = static_cast<QUICStreamImpl *>(quiche_conn_stream_application_data(this->_quiche_con, s));
+      stream->send_data(this->_quiche_con);
+    }
+    quiche_stream_iter_free(writable);
+  }
+
+  Ptr<IOBufferBlock> udp_payload;
+  quiche_send_info send_info;
+  ssize_t res;
+  ssize_t written = 0;
+
+  size_t quantum              = quiche_conn_send_quantum(this->_quiche_con);
+  size_t max_udp_payload_size = quiche_conn_max_send_udp_payload_size(this->_quiche_con);
+
+  // This buffer size must be less than 64KB because it can be used for UDP GSO (UDP_SEGMENT)
+  udp_payload = new_IOBufferBlock();
+  udp_payload->alloc(buffer_size_to_index(quantum, BUFFER_SIZE_INDEX_32K));
+  quantum = std::min(static_cast<int64_t>(quantum), udp_payload->write_avail());
+  while (written + max_udp_payload_size <= quantum) {
+    res = quiche_conn_send(this->_quiche_con, reinterpret_cast<uint8_t *>(udp_payload->end()) + written, max_udp_payload_size,
+                           &send_info);
+    if (res > 0) {
+      written += res;
+    }
+    if (static_cast<size_t>(res) != max_udp_payload_size) {
+      break;
+    }
+  }
+  if (written > 0) {
+    udp_payload->fill(written);
+    int segment_size = 0;
+    if (static_cast<size_t>(written) > max_udp_payload_size) {
+      segment_size = max_udp_payload_size;
+    }
+    this->_packet_handler->send_packet(this->_udp_con, this->con.addr, udp_payload, segment_size);
+    net_activity(this, this_ethread());
+  }
+}
+
+void
+QUICNetVConnection::_handle_interval()
+{
+  quiche_conn_on_timeout(this->_quiche_con);
+
+  if (quiche_conn_is_closed(this->_quiche_con)) {
+    this->_ctable->erase(this->_quic_connection_id, this);
+    this->_ctable->erase(this->_original_quic_connection_id, this);
+
+    if (quiche_conn_is_timed_out(this->_quiche_con)) {
+      this->thread->schedule_imm(this, VC_EVENT_INACTIVITY_TIMEOUT);
+      return;
+    }
+
+    bool is_app;
+    uint64_t error_code;
+    const uint8_t *reason;
+    size_t reason_len;
+    bool has_error = quiche_conn_peer_error(this->_quiche_con, &is_app, &error_code, &reason, &reason_len) ||
+                     quiche_conn_local_error(this->_quiche_con, &is_app, &error_code, &reason, &reason_len);
+    if (has_error && error_code != static_cast<uint64_t>(QUICTransErrorCode::NO_ERROR)) {
+      QUICConDebug("is_app=%d error_code=%" PRId64 " reason=%.*s", is_app, error_code, static_cast<int>(reason_len), reason);
+      this->thread->schedule_imm(this, VC_EVENT_ERROR);
+      return;
+    }
+
+    // If it's not timeout nor error, it's probably eos
+    this->thread->schedule_imm(this, VC_EVENT_EOS);
+
+  } else {
+    // Just schedule timeout event again if the connection is still open
+    this->thread->schedule_in(this, HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
+  }
+}
+
+int
+QUICNetVConnection::populate_protocol(std::string_view *results, int n) const
+{
+  return 0;
+}
+
+const char *
+QUICNetVConnection::protocol_contains(std::string_view tag) const
+{
+  return "";
+}
+
+SSL *
+QUICNetVConnection::_get_ssl_object() const
+{
+  return nullptr;
+}
+
+ssl_curve_id
+QUICNetVConnection::_get_tls_curve() const
+{
+  return 0;
+}
diff --git a/iocore/net/QUICNextProtocolAccept.cc b/iocore/net/QUICNextProtocolAccept.cc
index 53faf43..f3691e8 100644
--- a/iocore/net/QUICNextProtocolAccept.cc
+++ b/iocore/net/QUICNextProtocolAccept.cc
@@ -21,7 +21,7 @@
   limitations under the License.
  */
 
-#include "P_QUICNextProtocolAccept.h"
+#include "P_QUICNextProtocolAccept_native.h"
 
 static QUICNetVConnection *
 quic_netvc_cast(int event, void *edata)
diff --git a/iocore/net/QUICNextProtocolAccept_quiche.cc b/iocore/net/QUICNextProtocolAccept_quiche.cc
new file mode 100644
index 0000000..ed2256f
--- /dev/null
+++ b/iocore/net/QUICNextProtocolAccept_quiche.cc
@@ -0,0 +1,95 @@
+/** @file
+
+  QUICNextProtocolAccept
+
+  @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 "P_QUICNextProtocolAccept_quiche.h"
+
+static QUICNetVConnection *
+quic_netvc_cast(int event, void *edata)
+{
+  union {
+    VIO *vio;
+    NetVConnection *vc;
+  } ptr;
+
+  switch (event) {
+  case NET_EVENT_ACCEPT:
+    ptr.vc = static_cast<NetVConnection *>(edata);
+    return dynamic_cast<QUICNetVConnection *>(ptr.vc);
+  case VC_EVENT_INACTIVITY_TIMEOUT:
+  case VC_EVENT_READ_COMPLETE:
+  case VC_EVENT_ERROR:
+    ptr.vio = static_cast<VIO *>(edata);
+    return dynamic_cast<QUICNetVConnection *>(ptr.vio->vc_server);
+  default:
+    return nullptr;
+  }
+}
+
+int
+QUICNextProtocolAccept::mainEvent(int event, void *edata)
+{
+  QUICNetVConnection *netvc = quic_netvc_cast(event, edata);
+
+  Debug("v_quic", "[%s] event %d netvc %p", netvc->cids().data(), event, netvc);
+  switch (event) {
+  case NET_EVENT_ACCEPT:
+    ink_release_assert(netvc != nullptr);
+    netvc->registerNextProtocolSet(&this->protoset, this->protoenabled);
+    return EVENT_CONT;
+  default:
+    netvc->do_io_close();
+    return EVENT_DONE;
+  }
+}
+
+bool
+QUICNextProtocolAccept::accept(NetVConnection *, MIOBuffer *, IOBufferReader *)
+{
+  ink_release_assert(0);
+  return false;
+}
+
+bool
+QUICNextProtocolAccept::registerEndpoint(const char *protocol, Continuation *handler)
+{
+  return this->protoset.registerEndpoint(protocol, handler);
+}
+
+void
+QUICNextProtocolAccept::enableProtocols(const SessionProtocolSet &protos)
+{
+  this->protoenabled = protos;
+}
+
+QUICNextProtocolAccept::QUICNextProtocolAccept() : SessionAccept(nullptr)
+{
+  SET_HANDLER(&QUICNextProtocolAccept::mainEvent);
+}
+
+SSLNextProtocolSet *
+QUICNextProtocolAccept::getProtoSet()
+{
+  return &this->protoset;
+}
+
+QUICNextProtocolAccept::~QUICNextProtocolAccept() {}
diff --git a/iocore/net/QUICPacketHandler.cc b/iocore/net/QUICPacketHandler.cc
index a30e9be..d555b74 100644
--- a/iocore/net/QUICPacketHandler.cc
+++ b/iocore/net/QUICPacketHandler.cc
@@ -167,7 +167,7 @@
 QUICPacketHandlerIn::acceptEvent(int event, void *data)
 {
   // NetVConnection *netvc;
-  ink_release_assert(event == NET_EVENT_DATAGRAM_OPEN || event == NET_EVENT_DATAGRAM_READ_READY ||
+  ink_release_assert(event == EVENT_IMMEDIATE || event == NET_EVENT_DATAGRAM_OPEN || event == NET_EVENT_DATAGRAM_READ_READY ||
                      event == NET_EVENT_DATAGRAM_ERROR);
   ink_release_assert((event == NET_EVENT_DATAGRAM_OPEN) ? (data != nullptr) : (1));
   ink_release_assert((event == NET_EVENT_DATAGRAM_READ_READY) ? (data != nullptr) : (1));
@@ -186,6 +186,11 @@
       this->_recv_packet(event, packet_r);
     }
     return EVENT_CONT;
+  } else if (event == EVENT_IMMEDIATE) {
+    this->setThreadAffinity(this_ethread());
+    SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+    udpNet.UDPBind((Continuation *)this, &this->server.accept_addr.sa, -1, 1048576, 1048576);
+    return EVENT_CONT;
   }
 
   /////////////////
@@ -202,7 +207,17 @@
 void
 QUICPacketHandlerIn::init_accept(EThread *t = nullptr)
 {
+  int i, n;
+
   SET_HANDLER(&QUICPacketHandlerIn::acceptEvent);
+
+  n = eventProcessor.thread_group[ET_UDP]._count;
+  for (i = 0; i < n; i++) {
+    NetAccept *a = (i < n - 1) ? clone() : this;
+    EThread *t   = eventProcessor.thread_group[ET_UDP]._thread[i];
+    a->mutex     = get_NetHandler(t)->mutex;
+    t->schedule_imm(a);
+  }
 }
 
 Continuation *
diff --git a/iocore/net/QUICPacketHandler_quiche.cc b/iocore/net/QUICPacketHandler_quiche.cc
new file mode 100644
index 0000000..eb5ca22
--- /dev/null
+++ b/iocore/net/QUICPacketHandler_quiche.cc
@@ -0,0 +1,339 @@
+
+/** @file
+
+  @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 "tscore/I_Layout.h"
+#include "tscore/ink_config.h"
+#include "P_Net.h"
+
+#include "P_QUICNet.h"
+#include "P_QUICPacketHandler_quiche.h"
+#include "P_QUICNetProcessor_quiche.h"
+#include "P_QUICClosedConCollector.h"
+#include "quic/QUICConnectionTable.h"
+#include <quiche.h>
+
+static constexpr char debug_tag[]   = "quic_sec";
+static constexpr char v_debug_tag[] = "v_quic_sec";
+
+#define QUICDebug(fmt, ...) Debug(debug_tag, fmt, ##__VA_ARGS__)
+#define QUICPHDebug(dcid, scid, fmt, ...) \
+  Debug(debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
+#define QUICVPHDebug(dcid, scid, fmt, ...) \
+  Debug(v_debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
+
+QUICPacketHandler::QUICPacketHandler()
+{
+  this->_closed_con_collector        = new QUICClosedConCollector;
+  this->_closed_con_collector->mutex = new_ProxyMutex();
+}
+
+QUICPacketHandler::~QUICPacketHandler()
+{
+  if (this->_collector_event != nullptr) {
+    this->_collector_event->cancel();
+    this->_collector_event = nullptr;
+  }
+
+  if (this->_closed_con_collector != nullptr) {
+    delete this->_closed_con_collector;
+    this->_closed_con_collector = nullptr;
+  }
+}
+
+void
+QUICPacketHandler::close_connection(QUICNetVConnection *conn)
+{
+  int isin = ink_atomic_swap(&conn->in_closed_queue, 1);
+  if (!isin) {
+    this->_closed_con_collector->closedQueue.push(conn);
+  }
+}
+
+void
+QUICPacketHandler::send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload, uint16_t segment_size)
+{
+  UDPPacket *udp_packet = new_UDPPacket(addr, 0, udp_payload, segment_size);
+
+  if (is_debug_tag_set(v_debug_tag)) {
+    ip_port_text_buffer ipb;
+    QUICConnectionId dcid = QUICConnectionId::ZERO();
+    QUICConnectionId scid = QUICConnectionId::ZERO();
+
+    const uint8_t *buf = reinterpret_cast<uint8_t *>(udp_payload->buf());
+    uint64_t buf_len   = udp_payload->size();
+
+    if (!QUICInvariants::dcid(dcid, buf, buf_len)) {
+      ink_assert(false);
+    }
+
+    if (QUICInvariants::is_long_header(buf)) {
+      if (!QUICInvariants::scid(scid, buf, buf_len)) {
+        ink_assert(false);
+      }
+    }
+
+    QUICVPHDebug(dcid, scid, "send %s packet to %s from port %u size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
+                 ats_ip_nptop(&addr, ipb, sizeof(ipb)), udp_con->getPortNum(), buf_len);
+  }
+
+  udp_con->send(this->_get_continuation(), udp_packet);
+  get_UDPNetHandler(static_cast<UnixUDPConnection *>(udp_con)->ethread)->signalActivity();
+}
+
+QUICPacketHandlerIn::QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, quiche_config &config)
+  : NetAccept(opt), QUICPacketHandler(), _ctable(ctable), _quiche_config(config)
+{
+  this->mutex = new_ProxyMutex();
+}
+
+QUICPacketHandlerIn::~QUICPacketHandlerIn() {}
+
+NetProcessor *
+QUICPacketHandlerIn::getNetProcessor() const
+{
+  return &quic_NetProcessor;
+}
+
+NetAccept *
+QUICPacketHandlerIn::clone() const
+{
+  NetAccept *na;
+  na  = new QUICPacketHandlerIn(opt, this->_ctable, this->_quiche_config);
+  *na = *this;
+  return na;
+}
+
+int
+QUICPacketHandlerIn::acceptEvent(int event, void *data)
+{
+  // NetVConnection *netvc;
+  ink_release_assert(event == EVENT_IMMEDIATE || event == NET_EVENT_DATAGRAM_OPEN || event == NET_EVENT_DATAGRAM_READ_READY ||
+                     event == NET_EVENT_DATAGRAM_ERROR);
+  ink_release_assert((event == NET_EVENT_DATAGRAM_OPEN) ? (data != nullptr) : (1));
+  ink_release_assert((event == NET_EVENT_DATAGRAM_READ_READY) ? (data != nullptr) : (1));
+
+  if (event == NET_EVENT_DATAGRAM_OPEN) {
+    // Nothing to do.
+    return EVENT_CONT;
+  } else if (event == NET_EVENT_DATAGRAM_READ_READY) {
+    if (this->_collector_event == nullptr) {
+      this->_collector_event = this_ethread()->schedule_every(this->_closed_con_collector, HRTIME_MSECONDS(100));
+    }
+
+    Queue<UDPPacket> *queue = static_cast<Queue<UDPPacket> *>(data);
+    UDPPacket *packet_r;
+    while ((packet_r = queue->dequeue())) {
+      this->_recv_packet(event, packet_r);
+    }
+    return EVENT_CONT;
+  } else if (event == EVENT_IMMEDIATE) {
+    this->setThreadAffinity(this_ethread());
+    SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+    udpNet.UDPBind((Continuation *)this, &this->server.accept_addr.sa, -1, 1048576, 1048576);
+    return EVENT_CONT;
+  }
+
+  /////////////////
+  // EVENT_ERROR //
+  /////////////////
+  if (((long)data) == -ECONNABORTED) {
+  }
+
+  ink_abort("QUIC accept received fatal error: errno = %d", -(static_cast<int>((intptr_t)data)));
+  return EVENT_CONT;
+}
+
+void
+QUICPacketHandlerIn::init_accept(EThread *t = nullptr)
+{
+  int i, n;
+
+  SET_HANDLER(&QUICPacketHandlerIn::acceptEvent);
+
+  n = eventProcessor.thread_group[ET_UDP]._count;
+  for (i = 0; i < n; i++) {
+    NetAccept *a = (i < n - 1) ? clone() : this;
+    EThread *t   = eventProcessor.thread_group[ET_UDP]._thread[i];
+    a->mutex     = get_NetHandler(t)->mutex;
+    t->schedule_imm(a);
+  }
+}
+
+Continuation *
+QUICPacketHandlerIn::_get_continuation()
+{
+  return static_cast<NetAccept *>(this);
+}
+
+void
+QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
+{
+  // Assumption: udp_packet has only one IOBufferBlock
+  IOBufferBlock *block = udp_packet->getIOBlockChain();
+  const uint8_t *buf   = reinterpret_cast<uint8_t *>(block->buf());
+  uint64_t buf_len     = block->size();
+
+  constexpr int MAX_TOKEN_LEN             = 1200;
+  constexpr int DEFAULT_MAX_DATAGRAM_SIZE = 1350;
+  uint8_t type;
+  uint32_t version;
+  uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+  size_t scid_len = sizeof(scid);
+  uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];
+  size_t dcid_len = sizeof(dcid);
+  uint8_t token[MAX_TOKEN_LEN];
+  size_t token_len = sizeof(token);
+
+  int rc = quiche_header_info(buf, buf_len, QUICConnectionId::SCID_LEN, &version, &type, scid, &scid_len, dcid, &dcid_len, token,
+                              &token_len);
+  if (rc < 0) {
+    QUICDebug("Ignore packet - failed to parse header");
+    udp_packet->free();
+    return;
+  }
+
+  if (dcid_len > 255 || scid_len > 255) {
+    QUICDebug("Ignore packet - too long connection id");
+    udp_packet->free();
+    return;
+  }
+  QUICConnection *qc     = this->_ctable.lookup({dcid, static_cast<uint8_t>(dcid_len)});
+  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(qc);
+
+  EThread *eth = nullptr;
+  if (vc == nullptr) {
+    if (!quiche_version_is_supported(version)) {
+      Ptr<IOBufferBlock> udp_payload(new_IOBufferBlock());
+      udp_payload->alloc(iobuffer_size_to_index(DEFAULT_MAX_DATAGRAM_SIZE, BUFFER_SIZE_INDEX_2K));
+      QUICPHDebug(QUICConnectionId(scid, scid_len), QUICConnectionId(dcid, dcid_len), "Unsupported version: 0x%x", version);
+      ssize_t written = quiche_negotiate_version(scid, scid_len, dcid, dcid_len, reinterpret_cast<uint8_t *>(udp_payload->end()),
+                                                 udp_payload->write_avail());
+      udp_payload->fill(written);
+      this->send_packet(udp_packet->getConnection(), udp_packet->from, udp_payload);
+      udp_packet->free();
+      return;
+    }
+
+    QUICConfig::scoped_config params;
+    if (params->stateless_retry() && token_len == 0) {
+      QUICConnectionId new_cid;
+      new_cid.randomize();
+      QUICRetryToken retry_token = {udp_packet->from, {dcid, static_cast<uint8_t>(dcid_len)}, new_cid};
+      Ptr<IOBufferBlock> udp_payload(new_IOBufferBlock());
+      udp_payload->alloc(iobuffer_size_to_index(DEFAULT_MAX_DATAGRAM_SIZE, BUFFER_SIZE_INDEX_2K));
+      ssize_t written =
+        quiche_retry(scid, scid_len, dcid, dcid_len, new_cid, new_cid.length(), retry_token.buf(), retry_token.length(), version,
+                     reinterpret_cast<uint8_t *>(udp_payload->end()), udp_payload->write_avail());
+      udp_payload->fill(written);
+      this->send_packet(udp_packet->getConnection(), udp_packet->from, udp_payload);
+
+      udp_packet->free();
+      return;
+    }
+
+    // Create a new connection
+    Connection con;
+    con.setRemote(&udp_packet->from.sa);
+
+    eth                           = eventProcessor.assign_thread(ET_NET);
+    QUICConnectionId original_cid = {dcid, static_cast<uint8_t>(dcid_len)};
+    QUICConnectionId peer_cid     = {scid, static_cast<uint8_t>(scid_len)};
+
+    if (is_debug_tag_set("quic_sec")) {
+      QUICPHDebug(peer_cid, original_cid, "client initial dcid=%s", original_cid.hex().c_str());
+    }
+
+    QUICRetryToken retry_token = {token, token_len};
+    if (params->stateless_retry() && !retry_token.is_valid(udp_packet->from)) {
+      fprintf(stderr, "invalid address validation token\n");
+      udp_packet->free();
+      return;
+    }
+
+    QUICConnectionId new_cid;
+    quiche_conn *quiche_con =
+      quiche_accept(new_cid, new_cid.length(), retry_token.original_dcid(), retry_token.original_dcid().length(),
+#ifdef HAVE_QUICHE_CONFIG_SET_ACTIVE_CONNECTION_ID_LIMIT
+                    &udp_packet->to.sa, udp_packet->to.isIp4() ? sizeof(udp_packet->to.sin) : sizeof(udp_packet->to.sin6),
+#endif
+                    &udp_packet->from.sa, udp_packet->from.isIp4() ? sizeof(udp_packet->from.sin) : sizeof(udp_packet->from.sin6),
+                    &this->_quiche_config);
+
+    if (params->qlog_dir() != nullptr) {
+      char qlog_filepath[PATH_MAX];
+      const uint8_t *quic_trace_id;
+      size_t quic_trace_id_len = 0;
+      quiche_conn_trace_id(quiche_con, &quic_trace_id, &quic_trace_id_len);
+      sprintf(qlog_filepath, "%s/%.*s.sqlog", Layout::get()->relative(params->qlog_dir()).c_str(),
+              static_cast<int>(quic_trace_id_len), quic_trace_id);
+      quiche_conn_set_qlog_path(quiche_con, qlog_filepath, "ats", "");
+    }
+
+    vc = static_cast<QUICNetVConnection *>(getNetProcessor()->allocate_vc(nullptr));
+    // TODO Extract OCID and RCID from the token
+    // vc->init(version, peer_cid, original_cid, ocid_in_retry_token, rcid_in_retry_token, udp_packet->getConnection(), this,
+    // &this->_ctable);
+    vc->init(version, peer_cid, new_cid, QUICConnectionId::ZERO(), QUICConnectionId::ZERO(), udp_packet->getConnection(),
+             quiche_con, this, &this->_ctable);
+    vc->id = net_next_connection_number();
+    vc->con.move(con);
+    vc->submit_time = Thread::get_hrtime();
+    vc->thread      = eth;
+    vc->mutex       = new_ProxyMutex();
+    vc->action_     = *this->action_;
+    vc->set_is_transparent(this->opt.f_inbound_transparent);
+    vc->set_context(NET_VCONNECTION_IN);
+    vc->options.ip_proto  = NetVCOptions::USE_UDP;
+    vc->options.ip_family = udp_packet->from.sa.sa_family;
+    eth->schedule_imm(vc, EVENT_NONE, nullptr);
+    qc = vc;
+  } else if (vc && vc->in_closed_queue) {
+    // TODO Send stateless reset
+    udp_packet->free();
+    return;
+  }
+  eth = vc->thread;
+
+  QUICPollEvent *qe = quicPollEventAllocator.alloc();
+  qe->init(qc, static_cast<UDPPacketInternal *>(udp_packet));
+  // Push the packet into QUICPollCont
+  get_QUICPollCont(eth)->inQueue.push(qe);
+  get_NetHandler(eth)->signalActivity();
+
+  return;
+}
+
+void
+QUICPacketHandlerOut::init(QUICNetVConnection *vc)
+{
+}
+
+Continuation *
+QUICPacketHandlerOut::_get_continuation()
+{
+  return this;
+}
+
+void
+QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet)
+{
+}
diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc
index f0a9513..e2fd207 100644
--- a/iocore/net/SSLCertLookup.cc
+++ b/iocore/net/SSLCertLookup.cc
@@ -148,7 +148,7 @@
  * @param dst Output buffer.
  */
 inline void
-transform_lower(std::string_view src, ts::MemSpan<char> dst)
+transform_lower(std::string_view src, swoc::MemSpan<char> dst)
 {
   if (src.size() > dst.size() - 1) { // clip @a src, reserving space for the terminal nul.
     src = std::string_view{src.data(), dst.size() - 1};
diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc
index 67615ee..f2b7c69 100644
--- a/iocore/net/SSLConfig.cc
+++ b/iocore/net/SSLConfig.cc
@@ -261,6 +261,15 @@
   }
 #endif
 
+  // Read in the protocol string for ALPN to origin
+  char *clientALPNProtocols = nullptr;
+  REC_ReadConfigStringAlloc(clientALPNProtocols, "proxy.config.ssl.client.alpn_protocols");
+
+  if (clientALPNProtocols) {
+    this->alpn_protocols_array_size = MAX_ALPN_STRING;
+    convert_alpn_to_wire_format(clientALPNProtocols, this->alpn_protocols_array, this->alpn_protocols_array_size);
+  }
+
 #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
   REC_ReadConfigInteger(option, "proxy.config.ssl.server.honor_cipher_order");
   if (option) {
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index c3a5771..6f0fec1 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -1173,6 +1173,16 @@
         return EVENT_ERROR;
       }
 
+      // If it is negative, we are consciously not setting ALPN (e.g. for private server sessions)
+      if (options.alpn_protocols_array_size >= 0) {
+        if (options.alpn_protocols_array_size > 0) {
+          SSL_set_alpn_protos(this->ssl, options.alpn_protocols_array, options.alpn_protocols_array_size);
+        } else if (params->alpn_protocols_array_size > 0) {
+          // Set the ALPN protocols we are requesting.
+          SSL_set_alpn_protos(this->ssl, params->alpn_protocols_array, params->alpn_protocols_array_size);
+        }
+      }
+
       SSL_set_verify(this->ssl, SSL_VERIFY_PEER, verify_callback);
 
       // SNI
@@ -1384,9 +1394,9 @@
         }
         this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto), static_cast<size_t>(len)});
 
-        Debug("ssl", "client selected next protocol '%.*s'", len, proto);
+        Debug("ssl", "Origin selected next protocol '%.*s'", len, proto);
       } else {
-        Debug("ssl", "client did not select a next protocol");
+        Debug("ssl", "Origin did not select a next protocol");
       }
     }
 
@@ -1533,6 +1543,17 @@
         X509_free(cert);
       }
     }
+    {
+      unsigned char const *proto = nullptr;
+      unsigned int len           = 0;
+      // Make note of the negotiated protocol
+      SSL_get0_alpn_selected(ssl, &proto, &len);
+      if (len == 0) {
+        SSL_get0_next_proto_negotiated(ssl, &proto, &len);
+      }
+      Debug("ssl_alpn", "Negotiated ALPN: %.*s", len, proto);
+      this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto), static_cast<size_t>(len)});
+    }
 
     // if the handshake is complete and write is enabled reschedule the write
     if (closed == 0 && write.enabled) {
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index f05ca5e..1a2fe1c 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -2075,8 +2075,7 @@
       errPtr = parseConfigLine(line, &line_info, &sslCertTags);
       Debug("ssl_load", "currently parsing %s at line %d from config file: %s", line, line_num, params->configFilePath);
       if (errPtr != nullptr) {
-        RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s: discarding %s entry at line %d: %s", __func__, params->configFilePath,
-                         line_num, errPtr);
+        Warning("%s: discarding %s entry at line %d: %s", __func__, params->configFilePath, line_num, errPtr);
       } else {
         if (ssl_extract_certificate(&line_info, sslMultiCertSettings.get())) {
           // There must be a certificate specified unless the tunnel action is set
diff --git a/iocore/net/UnixNetVConnection.cc b/iocore/net/UnixNetVConnection.cc
index 573d4c0..c36d143 100644
--- a/iocore/net/UnixNetVConnection.cc
+++ b/iocore/net/UnixNetVConnection.cc
@@ -350,7 +350,6 @@
   ProxyMutex *mutex = thread->mutex.get();
 
   NET_INCREMENT_DYN_STAT(net_calls_to_writetonet_stat);
-  NET_INCREMENT_DYN_STAT(net_calls_to_writetonet_afterpoll_stat);
 
   write_to_net_io(nh, vc, thread);
 }
diff --git a/iocore/net/UnixUDPConnection.cc b/iocore/net/UnixUDPConnection.cc
index 0385d6a..99d3d12 100644
--- a/iocore/net/UnixUDPConnection.cc
+++ b/iocore/net/UnixUDPConnection.cc
@@ -98,11 +98,9 @@
 }
 
 void
-UDPConnection::bindToThread(Continuation *c)
+UDPConnection::bindToThread(Continuation *c, EThread *t)
 {
   UnixUDPConnection *uc = (UnixUDPConnection *)this;
-  // add to new connections queue for EThread.
-  EThread *t = eventProcessor.assign_thread(ET_UDP);
   ink_assert(t);
   ink_assert(get_UDPNetHandler(t));
   uc->ethread = t;
diff --git a/iocore/net/UnixUDPNet.cc b/iocore/net/UnixUDPNet.cc
index 8b9be3a..46fa1e3 100644
--- a/iocore/net/UnixUDPNet.cc
+++ b/iocore/net/UnixUDPNet.cc
@@ -37,6 +37,12 @@
 #include "P_Net.h"
 #include "P_UDPNet.h"
 
+#include "netinet/udp.h"
+#ifndef UDP_SEGMENT
+// This is needed because old glibc may not have the constant even if Kernel supports it.
+#define UDP_SEGMENT 103
+#endif
+
 using UDPNetContHandler = int (UDPNetHandler::*)(int, void *);
 
 ClassAllocator<UDPPacketInternal> udpPacketAllocator("udpPacketAllocator");
@@ -63,9 +69,12 @@
 void
 initialize_thread_for_udp_net(EThread *thread)
 {
+  int enable_gso;
+  REC_ReadConfigInteger(enable_gso, "proxy.config.udp.enable_gso");
+
   UDPNetHandler *nh = get_UDPNetHandler(thread);
 
-  new (reinterpret_cast<ink_dummy_for_new *>(nh)) UDPNetHandler;
+  new (reinterpret_cast<ink_dummy_for_new *>(nh)) UDPNetHandler(enable_gso);
   new (reinterpret_cast<ink_dummy_for_new *>(get_UDPPollCont(thread))) PollCont(thread->mutex);
   // The UDPNetHandler cannot be accessed across EThreads.
   // Because the UDPNetHandler should be called back immediately after UDPPollCont.
@@ -102,6 +111,13 @@
 #endif
 }
 
+EventType
+UDPNetProcessorInternal::register_event_type()
+{
+  ET_UDP = eventProcessor.register_event_type("ET_UDP");
+  return ET_UDP;
+}
+
 int
 UDPNetProcessorInternal::start(int n_upd_threads, size_t stacksize)
 {
@@ -112,7 +128,6 @@
   pollCont_offset      = eventProcessor.allocate(sizeof(PollCont));
   udpNetHandler_offset = eventProcessor.allocate(sizeof(UDPNetHandler));
 
-  ET_UDP = eventProcessor.register_event_type("ET_UDP");
   eventProcessor.schedule_spawn(&initialize_thread_for_udp_net, ET_UDP);
   eventProcessor.spawn_event_threads(ET_UDP, n_upd_threads, stacksize);
 
@@ -806,6 +821,18 @@
     goto Lerror;
   }
 
+  if (safe_setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, SOCKOPT_ON, sizeof(int)) < 0) {
+    Debug("udpnet", "setsockopt for SO_REUSEPORT failed");
+    goto Lerror;
+  }
+
+#ifdef SO_REUSEPORT_LB
+  if (safe_setsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, SOCKOPT_ON, sizeof(int)) < 0) {
+    Debug("udpnet", "setsockopt for SO_REUSEPORT_LB failed");
+    goto Lerror;
+  }
+#endif
+
   if (need_bind && (SocketManager::ink_bind(fd, addr, ats_ip_size(addr)) < 0)) {
     Debug("udpnet", "ink_bind failed");
     goto Lerror;
@@ -828,7 +855,7 @@
 
   Debug("udpnet", "UDPNetProcessor::UDPBind: %p fd=%d", n, fd);
   n->setBinding(&myaddr.sa);
-  n->bindToThread(cont);
+  n->bindToThread(cont, cont->getThreadAffinity());
 
   pc = get_UDPPollCont(n->ethread);
   pd = pc->pollDescriptor;
@@ -848,7 +875,16 @@
 }
 
 // send out all packets that need to be sent out as of time=now
-UDPQueue::UDPQueue() {}
+#ifdef SOL_UDP
+UDPQueue::UDPQueue(bool enable_gso) : use_udp_gso(enable_gso) {}
+#else
+UDPQueue::UDPQueue(bool enable_gso)
+{
+  if (enable_gso) {
+    Warning("Attempted to use UDP GSO per configuration, but it is unavailable");
+  }
+}
+#endif
 
 UDPQueue::~UDPQueue() {}
 
@@ -909,13 +945,23 @@
   ink_hrtime now                    = Thread::get_hrtime_updated();
   ink_hrtime send_threshold_time    = now + SLOT_TIME;
   int32_t bytesThisSlot = INT_MAX, bytesUsed = 0;
-  int32_t bytesThisPipe, sentOne;
+  int32_t bytesThisPipe;
   int64_t pktLen;
 
   bytesThisSlot = INT_MAX;
 
+#ifdef UIO_MAXIOV
+  constexpr int N_MAX_PACKETS = UIO_MAXIOV; // The limit comes from sendmmsg
+#else
+  constexpr int N_MAX_PACKETS = 1024;
+#endif
+  UDPPacketInternal *packets[N_MAX_PACKETS];
+  int nsent;
+  int npackets;
+
 sendPackets:
-  sentOne       = false;
+  nsent         = 0;
+  npackets      = 0;
   bytesThisPipe = bytesThisSlot;
 
   while ((bytesThisPipe > 0) && (pipeInfo.firstPacket(send_threshold_time))) {
@@ -929,21 +975,25 @@
       goto next_pkt;
     }
 
-    SendUDPPacket(p, pktLen);
     bytesUsed += pktLen;
     bytesThisPipe -= pktLen;
+    packets[npackets++] = p;
   next_pkt:
-    sentOne = true;
-    p->free();
-
-    if (bytesThisPipe < 0) {
+    if (bytesThisPipe < 0 && npackets == N_MAX_PACKETS) {
       break;
     }
   }
 
+  if (npackets > 0) {
+    nsent = SendMultipleUDPPackets(packets, npackets);
+  }
+  for (int i = 0; i < nsent; ++i) {
+    packets[i]->free();
+  }
+
   bytesThisSlot -= bytesUsed;
 
-  if ((bytesThisSlot > 0) && sentOne) {
+  if ((bytesThisSlot > 0) && nsent) {
     // redistribute the slack...
     now = Thread::get_hrtime_updated();
     if (pipeInfo.firstPacket(now) == nullptr) {
@@ -959,51 +1009,133 @@
 }
 
 void
-UDPQueue::SendUDPPacket(UDPPacketInternal *p, int32_t /* pktLen ATS_UNUSED */)
+UDPQueue::SendUDPPacket(UDPPacketInternal *p)
 {
   struct msghdr msg;
   struct iovec iov[32];
-  int n, count, iov_len = 0;
+  int n, count = 0;
 
   p->conn->lastSentPktStartTime = p->delivery_time;
   Debug("udp-send", "Sending %p", p);
 
-#if !defined(solaris)
   msg.msg_control    = nullptr;
   msg.msg_controllen = 0;
   msg.msg_flags      = 0;
-#endif
-  msg.msg_name    = reinterpret_cast<caddr_t>(&p->to.sa);
-  msg.msg_namelen = ats_ip_size(p->to);
-  iov_len         = 0;
+  msg.msg_name       = reinterpret_cast<caddr_t>(&p->to.sa);
+  msg.msg_namelen    = ats_ip_size(p->to);
 
-  for (IOBufferBlock *b = p->chain.get(); b != nullptr; b = b->next.get()) {
-    iov[iov_len].iov_base = static_cast<caddr_t>(b->start());
-    iov[iov_len].iov_len  = b->size();
-    iov_len++;
-  }
-  msg.msg_iov    = iov;
-  msg.msg_iovlen = iov_len;
+  if (p->segment_size > 0) {
+    ink_assert(p->chain->next == nullptr);
+    msg.msg_iov    = iov;
+    msg.msg_iovlen = 1;
+#ifdef SOL_UDP
+    if (use_udp_gso) {
+      iov[0].iov_base = p->chain.get()->start();
+      iov[0].iov_len  = p->chain.get()->size();
 
-  count = 0;
-  while (true) {
-    // stupid Linux problem: sendmsg can return EAGAIN
-    n = ::sendmsg(p->conn->getFd(), &msg, 0);
-    if ((n >= 0) || (errno != EAGAIN)) {
-      // send succeeded or some random error happened.
-      if (n < 0) {
-        Debug("udp-send", "Error: %s (%d)", strerror(errno), errno);
+      union udp_segment_hdr {
+        char buf[CMSG_SPACE(sizeof(uint16_t))];
+        struct cmsghdr align;
+      } u;
+      msg.msg_control    = u.buf;
+      msg.msg_controllen = sizeof(u.buf);
+
+      struct cmsghdr *cm           = CMSG_FIRSTHDR(&msg);
+      cm->cmsg_level               = SOL_UDP;
+      cm->cmsg_type                = UDP_SEGMENT;
+      cm->cmsg_len                 = CMSG_LEN(sizeof(uint16_t));
+      *((uint16_t *)CMSG_DATA(cm)) = p->segment_size;
+
+      count = 0;
+      while (true) {
+        // stupid Linux problem: sendmsg can return EAGAIN
+        n = ::sendmsg(p->conn->getFd(), &msg, 0);
+        if (n >= 0) {
+          break;
+        }
+        if (errno == EIO && use_udp_gso) {
+          Warning("Disabling UDP GSO due to an error");
+          use_udp_gso = false;
+          SendUDPPacket(p);
+          return;
+        }
+        if (errno == EAGAIN) {
+          ++count;
+          if ((g_udp_numSendRetries > 0) && (count >= g_udp_numSendRetries)) {
+            // tried too many times; give up
+            Debug("udpnet", "Send failed: too many retries");
+            return;
+          }
+        } else {
+          Debug("udp-send", "Error: %s (%d)", strerror(errno), errno);
+          return;
+        }
       }
+    } else {
+#endif
+      // Send segments seprately if UDP_SEGMENT is not supported
+      int offset = 0;
+      while (offset < p->chain.get()->size()) {
+        iov[0].iov_base = p->chain.get()->start() + offset;
+        iov[0].iov_len = std::min(static_cast<long>(p->segment_size), p->chain.get()->end() - static_cast<char *>(iov[0].iov_base));
 
-      break;
+        count = 0;
+        while (true) {
+          // stupid Linux problem: sendmsg can return EAGAIN
+          n = ::sendmsg(p->conn->getFd(), &msg, 0);
+          if (n >= 0) {
+            break;
+          }
+          if (errno == EAGAIN) {
+            ++count;
+            if ((g_udp_numSendRetries > 0) && (count >= g_udp_numSendRetries)) {
+              // tried too many times; give up
+              Debug("udpnet", "Send failed: too many retries");
+              return;
+            }
+          } else {
+            Debug("udp-send", "Error: %s (%d)", strerror(errno), errno);
+            return;
+          }
+        }
+
+        offset += iov[0].iov_len;
+      }
+      ink_assert(offset == p->chain.get()->size());
+#ifdef SOL_UDP
+    } // use_udp_segment
+#endif
+  } else {
+    // Nothing is special
+    int iov_len = 0;
+    for (IOBufferBlock *b = p->chain.get(); b != nullptr; b = b->next.get()) {
+      iov[iov_len].iov_base = static_cast<caddr_t>(b->start());
+      iov[iov_len].iov_len  = b->size();
+      iov_len++;
     }
-    if (errno == EAGAIN) {
-      ++count;
-      if ((g_udp_numSendRetries > 0) && (count >= g_udp_numSendRetries)) {
-        // tried too many times; give up
-        Debug("udpnet", "Send failed: too many retries");
+    msg.msg_iov    = iov;
+    msg.msg_iovlen = iov_len;
+
+    count = 0;
+    while (true) {
+      // stupid Linux problem: sendmsg can return EAGAIN
+      n = ::sendmsg(p->conn->getFd(), &msg, 0);
+      if ((n >= 0) || (errno != EAGAIN)) {
+        // send succeeded or some random error happened.
+        if (n < 0) {
+          Debug("udp-send", "Error: %s (%d)", strerror(errno), errno);
+        }
+
         break;
       }
+      if (errno == EAGAIN) {
+        ++count;
+        if ((g_udp_numSendRetries > 0) && (count >= g_udp_numSendRetries)) {
+          // tried too many times; give up
+          Debug("udpnet", "Send failed: too many retries");
+          break;
+        }
+      }
     }
   }
 }
@@ -1015,6 +1147,161 @@
   outQueue.push((UDPPacketInternal *)p);
 }
 
+int
+UDPQueue::SendMultipleUDPPackets(UDPPacketInternal **p, uint16_t n)
+{
+#ifdef HAVE_SENDMMSG
+  struct mmsghdr *msgvec;
+  int msgvec_size;
+
+#ifdef SOL_UDP
+  union udp_segment_hdr {
+    char buf[CMSG_SPACE(sizeof(uint16_t))];
+    struct cmsghdr align;
+  };
+  if (use_udp_gso) {
+    msgvec_size = sizeof(struct mmsghdr) * n;
+  } else {
+    msgvec_size = sizeof(struct mmsghdr) * n * 64;
+  }
+#else
+  msgvec_size = sizeof(struct mmsghdr) * n * 64;
+#endif
+  msgvec = static_cast<struct mmsghdr *>(alloca(msgvec_size));
+  memset(msgvec, 0, msgvec_size);
+
+  int vlen = 0;
+  int fd   = p[0]->conn->getFd();
+  for (int i = 0; i < n; ++i) {
+    UDPPacketInternal *packet;
+    struct msghdr *msg;
+    struct iovec *iov;
+    int iov_len;
+
+    packet                             = p[i];
+    packet->conn->lastSentPktStartTime = packet->delivery_time;
+    ink_assert(packet->conn->getFd() == fd);
+    if (packet->segment_size > 0) {
+      // Presumes one big super buffer is given
+      ink_assert(packet->chain->next == nullptr);
+#ifdef SOL_UDP
+      if (use_udp_gso) {
+        msg              = &msgvec[vlen].msg_hdr;
+        msg->msg_name    = reinterpret_cast<caddr_t>(&packet->to.sa);
+        msg->msg_namelen = ats_ip_size(packet->to);
+
+        union udp_segment_hdr *u;
+        u                   = static_cast<union udp_segment_hdr *>(alloca(sizeof(union udp_segment_hdr)));
+        msg->msg_control    = u->buf;
+        msg->msg_controllen = sizeof(u->buf);
+        iov                 = static_cast<struct iovec *>(alloca(sizeof(struct iovec)));
+        iov_len             = 1;
+        iov->iov_base       = packet->chain.get()->start();
+        iov->iov_len        = packet->chain.get()->size();
+        msg->msg_iov        = iov;
+        msg->msg_iovlen     = iov_len;
+
+        struct cmsghdr *cm           = CMSG_FIRSTHDR(msg);
+        cm->cmsg_level               = SOL_UDP;
+        cm->cmsg_type                = UDP_SEGMENT;
+        cm->cmsg_len                 = CMSG_LEN(sizeof(uint16_t));
+        *((uint16_t *)CMSG_DATA(cm)) = packet->segment_size;
+        vlen++;
+      } else {
+#endif
+        // UDP_SEGMENT is unavailable
+        // Send the given data as multiple messages
+        int offset = 0;
+        while (offset < packet->chain.get()->size()) {
+          msg              = &msgvec[vlen].msg_hdr;
+          msg->msg_name    = reinterpret_cast<caddr_t>(&packet->to.sa);
+          msg->msg_namelen = ats_ip_size(packet->to);
+          iov              = static_cast<struct iovec *>(alloca(sizeof(struct iovec)));
+          iov_len          = 1;
+          iov->iov_base    = packet->chain.get()->start() + offset;
+          iov->iov_len =
+            std::min(packet->segment_size, static_cast<uint16_t>(packet->chain.get()->end() - static_cast<char *>(iov->iov_base)));
+          msg->msg_iov    = iov;
+          msg->msg_iovlen = iov_len;
+          offset += iov->iov_len;
+          vlen++;
+        }
+        ink_assert(offset == packet->chain.get()->size());
+#ifdef SOL_UDP
+      } // use_udp_gso
+#endif
+    } else {
+      // Nothing is special
+      msg              = &msgvec[vlen].msg_hdr;
+      msg->msg_name    = reinterpret_cast<caddr_t>(&packet->to.sa);
+      msg->msg_namelen = ats_ip_size(packet->to);
+      iov              = static_cast<struct iovec *>(alloca(sizeof(struct iovec) * 64));
+      iov_len          = 0;
+      for (IOBufferBlock *b = packet->chain.get(); b != nullptr; b = b->next.get()) {
+        iov[iov_len].iov_base = static_cast<caddr_t>(b->start());
+        iov[iov_len].iov_len  = b->size();
+        iov_len++;
+      }
+      msg->msg_iov    = iov;
+      msg->msg_iovlen = iov_len;
+      vlen++;
+    }
+  }
+
+  if (vlen == 0) {
+    return 0;
+  }
+
+  int res = ::sendmmsg(fd, msgvec, vlen, 0);
+  if (res < 0) {
+#ifdef SOL_UDP
+    if (use_udp_gso && errno == EIO) {
+      Warning("Disabling UDP GSO due to an error");
+      Debug("udp-send", "Disabling UDP GSO due to an error");
+      use_udp_gso = false;
+      return SendMultipleUDPPackets(p, n);
+    } else {
+      Debug("udp-send", "udp_gso=%d res=%d errno=%d", use_udp_gso, res, errno);
+      return res;
+    }
+#else
+    Debug("udp-send", "res=%d errno=%d", res, errno);
+    return res;
+#endif
+  }
+
+  if (res > 0) {
+#ifdef SOL_UDP
+    if (use_udp_gso) {
+      Debug("udp-send", "Sent %d messages by processing %d UDPPackets (GSO)", res, n);
+    } else {
+#endif
+      int i    = 0;
+      int nmsg = res;
+      for (i = 0; i < n && res > 0; ++i) {
+        if (p[i]->segment_size == 0) {
+          res -= 1;
+        } else {
+          res -= (p[i]->chain.get()->size() / p[i]->segment_size) + ((p[i]->chain.get()->size() % p[i]->segment_size) != 0);
+        }
+      }
+      Debug("udp-send", "Sent %d messages by processing %d UDPPackets", nmsg, i);
+      res = i;
+#ifdef SOL_UDP
+    }
+#endif
+  }
+
+  return res;
+#else
+  // sendmmsg is unavailable
+  for (int i = 0; i < n; ++i) {
+    SendUDPPacket(p[i]);
+  }
+  return n;
+#endif
+}
+
 #undef LINK
 
 static void
@@ -1031,7 +1318,7 @@
 #endif
 }
 
-UDPNetHandler::UDPNetHandler()
+UDPNetHandler::UDPNetHandler(bool enable_gso) : udpOutQueue(enable_gso)
 {
   nextCheck = Thread::get_hrtime_updated() + HRTIME_MSECONDS(1000);
   lastCheck = 0;
diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc
index 3f90fe9..03656b7 100644
--- a/iocore/net/YamlSNIConfig.cc
+++ b/iocore/net/YamlSNIConfig.cc
@@ -123,7 +123,6 @@
 TsEnumDescriptor TLS_PROTOCOLS_DESCRIPTOR = {{{"TLSv1", 0}, {"TLSv1_1", 1}, {"TLSv1_2", 2}, {"TLSv1_3", 3}}};
 
 std::set<std::string> valid_sni_config_keys = {TS_fqdn,
-                                               TS_disable_h2,
                                                TS_verify_client,
                                                TS_verify_client_ca_certs,
                                                TS_tunnel_route,
@@ -168,9 +167,6 @@
     } else {
       return false; // servername must be present
     }
-    if (node[TS_disable_h2]) {
-      item.offer_h2 = false;
-    }
     if (node[TS_http2]) {
       item.offer_h2 = node[TS_http2].as<bool>();
     }
diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h
index dbb79ba..b62d23e 100644
--- a/iocore/net/YamlSNIConfig.h
+++ b/iocore/net/YamlSNIConfig.h
@@ -32,7 +32,6 @@
 
 #define TSDECL(id) constexpr char TS_##id[] = #id
 TSDECL(fqdn);
-TSDECL(disable_h2);
 TSDECL(verify_client);
 TSDECL(verify_client_ca_certs);
 TSDECL(tunnel_route);
diff --git a/iocore/net/libinknet_stub.cc b/iocore/net/libinknet_stub.cc
index d9771d9..64c5efe 100644
--- a/iocore/net/libinknet_stub.cc
+++ b/iocore/net/libinknet_stub.cc
@@ -139,30 +139,6 @@
 LifecycleAPIHooks *lifecycle_hooks = nullptr;
 StatPagesManager statPagesManager;
 
-#include "ProcessManager.h"
-ProcessManager *pmgmt = nullptr;
-
-int
-BaseManager::registerMgmtCallback(int, MgmtCallback const &)
-{
-  ink_assert(false);
-  return 0;
-}
-
-void
-ProcessManager::signalManager(int, char const *, int)
-{
-  ink_assert(false);
-  return;
-}
-
-void
-ProcessManager::signalManager(int, char const *)
-{
-  ink_assert(false);
-  return;
-}
-
 #include "PreWarmManager.h"
 void
 PreWarmManager::reconfigure()
diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am
index a0c60fd..5546fc8 100644
--- a/iocore/net/quic/Makefile.am
+++ b/iocore/net/quic/Makefile.am
@@ -29,11 +29,13 @@
   -I$(abs_top_srcdir)/proxy/http3 \
   -I$(abs_top_srcdir)/mgmt \
   -I$(abs_top_srcdir)/mgmt/utils \
-  $(TS_INCLUDES) \
+  $(TS_INCLUDES) @SWOC_INCLUDES@ \
   @OPENSSL_INCLUDES@ @YAMLCPP_INCLUDES@
 
 noinst_LIBRARIES = libquic.a
 
+if USE_QUICHE
+else
 if OPENSSL_IS_BORINGSSL
 QUICPHProtector_impl = QUICPacketHeaderProtector_boringssl.cc
 QUICPPProtector_impl = QUICPacketPayloadProtector_boringssl.cc
@@ -47,7 +49,26 @@
 endif
 
 QLog_impl = qlog/QLogEvent.cc qlog/QLogFrame.cc qlog/QLog.cc
+endif
 
+
+if USE_QUICHE
+libquic_a_SOURCES = \
+  QUICApplication.cc \
+  QUICApplicationMap.cc \
+  QUICConfig.cc \
+  QUICContext.cc \
+  QUICConnectionTable.cc \
+  QUICGlobals.cc \
+  QUICTypes.cc \
+  QUICIntUtil.cc \
+  QUICStream.cc \
+  QUICStream_quiche.cc \
+  QUICStreamManager.cc \
+  QUICStreamManager_quiche.cc \
+  QUICStreamAdapter.cc \
+  QUICStreamVCAdapter.cc
+else
 libquic_a_SOURCES = \
   QUICGlobals.cc \
   QUICTypes.cc \
@@ -59,12 +80,14 @@
   QUICVersionNegotiator.cc \
   QUICLossDetector.cc \
   QUICStreamManager.cc \
+  QUICStreamManager_native.cc \
   QUICNewRenoCongestionController.cc \
   QUICFlowController.cc \
   QUICStreamState.cc \
   QUICStreamAdapter.cc \
   QUICStreamVCAdapter.cc \
   QUICStream.cc \
+  QUICStreamBase.cc \
   QUICHandshake.cc \
   QUICPacketHeaderProtector.cc \
   $(QUICPHProtector_impl) \
@@ -103,10 +126,13 @@
   QUICContext.cc \
   QUICTokenCreator.cc \
   $(QLog_impl)
+endif
 
 #
 # Check Programs
 #
+if USE_QUICHE
+else
 check_PROGRAMS = \
   test_QUICAckFrameCreator \
   test_QUICAltConnectionManager \
@@ -132,6 +158,7 @@
   test_QUICFrameRetransmitter \
   test_QUICAddrVerifyState \
   test_QUICPinger
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -149,7 +176,7 @@
   $(top_builddir)/src/tscpp/util/libtscpputil.la \
   $(top_builddir)/proxy/ParentSelectionStrategy.o \
   $(top_builddir)/iocore/net/TLSKeyLogger.o \
-  @HWLOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@
+  @HWLOC_LIBS@ @SWOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@ @YAMLCPP_LIBS@
 
 test_event_main_SOURCES = \
   ./test/event_processor_main.cc
diff --git a/iocore/net/quic/Mock.h b/iocore/net/quic/Mock.h
index 17cbf40..3b0b31e 100644
--- a/iocore/net/quic/Mock.h
+++ b/iocore/net/quic/Mock.h
@@ -27,16 +27,27 @@
 
 #include "QUICApplication.h"
 #include "QUICStreamManager.h"
+#if HAVE_QUICHE_H
+#include "QUICStreamManager_quiche.h"
+#else
+#include "QUICStreamManager_native.h"
+#endif
 #include "QUICLossDetector.h"
 #include "QUICPacketProtectionKeyInfo.h"
 #include "QUICPinger.h"
 #include "QUICPadder.h"
 #include "QUICEvents.h"
 #include "QUICPacketProtectionKeyInfo.h"
+#include "QUICPathManager.h"
 #include "QUICPinger.h"
 #include "QUICPadder.h"
 #include "QUICHandshakeProtocol.h"
 #include "QUICStreamAdapter.h"
+#if HAVE_QUICHE_H
+#include "QUICStream_quiche.h"
+#else
+#include "QUICStream_native.h"
+#endif
 
 class MockQUICContext;
 
@@ -238,12 +249,13 @@
   }
 };
 
-class MockQUICStreamManager : public QUICStreamManager
+class MockQUICStreamManager : public QUICStreamManagerImpl
 {
 public:
-  MockQUICStreamManager(QUICContext *context) : QUICStreamManager(context, nullptr) {}
+  MockQUICStreamManager(QUICContext *context) : QUICStreamManagerImpl(context, nullptr) {}
 
   // Override
+#ifndef HAVE_QUICHE_H
   virtual QUICConnectionErrorUPtr
   handle_frame(QUICEncryptionLevel level, const QUICFrame &f) override
   {
@@ -252,6 +264,7 @@
 
     return nullptr;
   }
+#endif
 
   // for Test
   int
@@ -650,6 +663,7 @@
   {
     return _config;
   }
+#ifndef HAVE_QUICHE_H
   virtual QUICRTTProvider *
   rtt_provider() const override
   {
@@ -679,6 +693,7 @@
   {
     return _path_manager.get();
   }
+#endif
 
 private:
   QUICConfig::scoped_config _config;
@@ -1069,7 +1084,7 @@
 
   QUICFrame *
   generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                 size_t current_packet_size, uint32_t seq_num) override
+                 size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override
   {
     QUICFrame *frame              = QUICFrameFactory::create_ping_frame(buf, 0, this);
     QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
diff --git a/iocore/net/quic/QUICAckFrameCreator.cc b/iocore/net/quic/QUICAckFrameCreator.cc
index bee2b76..2b49450 100644
--- a/iocore/net/quic/QUICAckFrameCreator.cc
+++ b/iocore/net/quic/QUICAckFrameCreator.cc
@@ -61,7 +61,8 @@
  */
 QUICFrame *
 QUICAckFrameManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                    uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
+                                    uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                    QUICFrameGenerator *owner)
 {
   QUICAckFrame *ack_frame = nullptr;
 
diff --git a/iocore/net/quic/QUICAckFrameCreator.h b/iocore/net/quic/QUICAckFrameCreator.h
index 55e20f2..ab1fabd 100644
--- a/iocore/net/quic/QUICAckFrameCreator.h
+++ b/iocore/net/quic/QUICAckFrameCreator.h
@@ -106,7 +106,7 @@
    * Calls create directly.
    */
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
   QUICFrameId issue_frame_id();
   uint8_t ack_delay_exponent() const;
diff --git a/iocore/net/quic/QUICAltConnectionManager.cc b/iocore/net/quic/QUICAltConnectionManager.cc
index 5226a1d..10f6ab5 100644
--- a/iocore/net/quic/QUICAltConnectionManager.cc
+++ b/iocore/net/quic/QUICAltConnectionManager.cc
@@ -299,7 +299,8 @@
  */
 QUICFrame *
 QUICAltConnectionManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                         uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
+                                         uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                         QUICFrameGenerator *owner)
 {
   QUICFrame *frame = nullptr;
   if (!this->_is_level_matched(level)) {
diff --git a/iocore/net/quic/QUICAltConnectionManager.h b/iocore/net/quic/QUICAltConnectionManager.h
index ade5f89..37d6837 100644
--- a/iocore/net/quic/QUICAltConnectionManager.h
+++ b/iocore/net/quic/QUICAltConnectionManager.h
@@ -82,7 +82,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
 private:
   struct AltConnectionInfo {
diff --git a/iocore/net/quic/QUICBidirectionalStream.cc b/iocore/net/quic/QUICBidirectionalStream.cc
index e5653e7..18c5449 100644
--- a/iocore/net/quic/QUICBidirectionalStream.cc
+++ b/iocore/net/quic/QUICBidirectionalStream.cc
@@ -29,7 +29,7 @@
 //
 QUICBidirectionalStream::QUICBidirectionalStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
                                                  uint64_t recv_max_stream_data, uint64_t send_max_stream_data)
-  : QUICStream(cinfo, sid),
+  : QUICStreamBase(cinfo, sid),
     _remote_flow_controller(send_max_stream_data, _id),
     _local_flow_controller(rtt_provider, recv_max_stream_data, _id),
     _flow_control_buffer_size(recv_max_stream_data),
@@ -190,9 +190,10 @@
 
 QUICFrame *
 QUICBidirectionalStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
-                                        uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
+                                        uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                        QUICFrameGenerator *owner)
 {
-  QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), this);
+  QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), owner);
   if (frame != nullptr) {
     ink_assert(frame->type() == QUICFrameType::STREAM);
     this->_records_stream_frame(level, *static_cast<QUICStreamFrame *>(frame));
@@ -201,7 +202,7 @@
 
   // RESET_STREAM
   if (this->_reset_reason && !this->_is_reset_sent) {
-    frame = QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason, this->_issue_frame_id(), this);
+    frame = QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason, this->_issue_frame_id(), owner);
     if (frame->size() > maximum_frame_size) {
       frame->~QUICFrame();
       return nullptr;
@@ -216,8 +217,8 @@
 
   // STOP_SENDING
   if (this->_stop_sending_reason && !this->_is_stop_sending_sent) {
-    frame =
-      QUICFrameFactory::create_stop_sending_frame(buf, this->id(), this->_stop_sending_reason->code, this->_issue_frame_id(), this);
+    frame = QUICFrameFactory::create_stop_sending_frame(buf, this->id(), this->_stop_sending_reason->code, this->_issue_frame_id(),
+                                                        owner);
     if (frame->size() > maximum_frame_size) {
       frame->~QUICFrame();
       return nullptr;
@@ -231,7 +232,8 @@
   }
 
   // MAX_STREAM_DATA
-  frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
+  frame =
+    this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num, owner);
   if (frame) {
     // maximum_frame_size should be checked in QUICFlowController
     return frame;
@@ -266,8 +268,8 @@
     uint64_t stream_credit = this->_remote_flow_controller.credit();
     if (stream_credit == 0) {
       // STREAM_DATA_BLOCKED
-      frame =
-        this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
+      frame = this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num,
+                                                           owner);
       return frame;
     }
 
@@ -292,7 +294,7 @@
   // STREAM - Pure FIN or data length is lager than 0
   // FIXME has_length_flag and has_offset_flag should be configurable
   frame = QUICFrameFactory::create_stream_frame(buf, block, this->_id, this->_send_offset, fin, true, true, this->_issue_frame_id(),
-                                                this);
+                                                owner);
   if (!this->_state.is_allowed_to_send(*frame)) {
     QUICStreamDebug("Canceled sending %s frame due to the stream state", QUICDebugNames::frame_type(frame->type()));
     return frame;
@@ -338,6 +340,12 @@
       this->_is_transfer_complete = true;
     }
     break;
+  case QUICFrameType::STREAM_DATA_BLOCKED:
+    this->_remote_flow_controller.on_frame_acked(info);
+    break;
+  case QUICFrameType::MAX_STREAM_DATA:
+    this->_local_flow_controller.on_frame_acked(info);
+    break;
   case QUICFrameType::STOP_SENDING:
   default:
     break;
@@ -367,6 +375,12 @@
   case QUICFrameType::STOP_SENDING:
     this->_is_stop_sending_sent = false;
     break;
+  case QUICFrameType::STREAM_DATA_BLOCKED:
+    this->_remote_flow_controller.on_frame_lost(info);
+    break;
+  case QUICFrameType::MAX_STREAM_DATA:
+    this->_local_flow_controller.on_frame_lost(info);
+    break;
   default:
     break;
   }
diff --git a/iocore/net/quic/QUICBidirectionalStream.h b/iocore/net/quic/QUICBidirectionalStream.h
index 6f0118b..3029527 100644
--- a/iocore/net/quic/QUICBidirectionalStream.h
+++ b/iocore/net/quic/QUICBidirectionalStream.h
@@ -24,14 +24,18 @@
 #pragma once
 
 #include "QUICStream.h"
+#include "QUICStream_native.h"
 
-class QUICBidirectionalStream : public QUICStream, public QUICTransferProgressProvider
+class QUICBidirectionalStream : public QUICStreamBase, public QUICTransferProgressProvider
 {
 public:
   QUICBidirectionalStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
                           uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
   QUICBidirectionalStream()
-    : QUICStream(), _remote_flow_controller(0, 0), _local_flow_controller(nullptr, 0, 0), _state(nullptr, nullptr, nullptr, nullptr)
+    : QUICStreamBase(),
+      _remote_flow_controller(0, 0),
+      _local_flow_controller(nullptr, 0, 0),
+      _state(nullptr, nullptr, nullptr, nullptr)
   {
   }
 
@@ -40,7 +44,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
   virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame) override;
   virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame) override;
diff --git a/iocore/net/quic/QUICContext.cc b/iocore/net/quic/QUICContext.cc
index b065f56..7de91ea 100644
--- a/iocore/net/quic/QUICContext.cc
+++ b/iocore/net/quic/QUICContext.cc
@@ -94,16 +94,20 @@
   const QUICConfigParams *_params;
 };
 
+#if HAVE_QUICHE_H
+QUICContext::QUICContext(QUICConnectionInfoProvider *info) : _connection_info(info) {}
+#else
 QUICContext::QUICContext(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
                          QUICPathManager *path_manager)
-  : _key_info(key_info),
-    _connection_info(info),
+  : _connection_info(info),
+    _key_info(key_info),
     _rtt_provider(rtt),
     _path_manager(path_manager),
     _ld_config(std::make_unique<QUICLDConfigQCP>(_config)),
     _cc_config(std::make_unique<QUICCCConfigQCP>(_config))
 {
 }
+#endif
 
 QUICConnectionInfoProvider *
 QUICContext::connection_info() const
@@ -117,6 +121,8 @@
   return _config;
 }
 
+#if HAVE_QUICHE_H
+#else
 QUICPacketProtectionKeyInfoProvider *
 QUICContext::key_info() const
 {
@@ -146,3 +152,4 @@
 {
   return _path_manager;
 }
+#endif
diff --git a/iocore/net/quic/QUICContext.h b/iocore/net/quic/QUICContext.h
index 0469b4c..81eb761 100644
--- a/iocore/net/quic/QUICContext.h
+++ b/iocore/net/quic/QUICContext.h
@@ -72,17 +72,24 @@
 class QUICContext
 {
 public:
+#if HAVE_QUICHE_H
+  QUICContext(QUICConnectionInfoProvider *info);
+#else
   QUICContext(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
               QUICPathManager *path_manager);
+#endif
 
   virtual ~QUICContext(){};
   virtual QUICConnectionInfoProvider *connection_info() const;
   virtual QUICConfig::scoped_config config() const;
+#if HAVE_QUICHE_H
+#else
   virtual QUICLDConfig &ld_config() const;
   virtual QUICPacketProtectionKeyInfoProvider *key_info() const;
   virtual QUICCCConfig &cc_config() const;
   virtual QUICRTTProvider *rtt_provider() const;
   virtual QUICPathManager *path_manager() const;
+#endif
 
   // register a callback which will be called when specified event happen.
   void
@@ -183,13 +190,16 @@
 
 private:
   QUICConfig::scoped_config _config;
+  QUICConnectionInfoProvider *_connection_info = nullptr;
+#if HAVE_QUICHE_H
+#else
   QUICPacketProtectionKeyInfoProvider *_key_info = nullptr;
-  QUICConnectionInfoProvider *_connection_info   = nullptr;
   QUICRTTProvider *_rtt_provider                 = nullptr;
   QUICPathManager *_path_manager                 = nullptr;
 
   std::unique_ptr<QUICLDConfig> _ld_config = nullptr;
   std::unique_ptr<QUICCCConfig> _cc_config = nullptr;
+#endif
 
   std::vector<std::shared_ptr<QUICCallback>> _callbacks;
 };
diff --git a/iocore/net/quic/QUICCryptoStream.cc b/iocore/net/quic/QUICCryptoStream.cc
index ba3afa2..556918e 100644
--- a/iocore/net/quic/QUICCryptoStream.cc
+++ b/iocore/net/quic/QUICCryptoStream.cc
@@ -112,7 +112,8 @@
  */
 QUICFrame *
 QUICCryptoStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                 uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
+                                 uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                 QUICFrameGenerator *owner)
 {
   QUICConnectionErrorUPtr error = nullptr;
 
@@ -120,7 +121,7 @@
     return QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason);
   }
 
-  QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), this);
+  QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), owner);
   if (frame != nullptr) {
     ink_assert(frame->type() == QUICFrameType::CRYPTO);
     this->_records_crypto_frame(level, *static_cast<QUICCryptoFrame *>(frame));
@@ -143,7 +144,7 @@
   block->_end = std::min(block->start() + frame_payload_size, block->_buf_end);
   ink_assert(static_cast<uint64_t>(block->read_avail()) == frame_payload_size);
 
-  frame = QUICFrameFactory::create_crypto_frame(buf, block, this->_send_offset, this->_issue_frame_id(), this);
+  frame = QUICFrameFactory::create_crypto_frame(buf, block, this->_send_offset, this->_issue_frame_id(), owner);
   this->_send_offset += frame_payload_size;
   this->_write_buffer_reader->consume(frame_payload_size);
   this->_records_crypto_frame(level, *static_cast<QUICCryptoFrame *>(frame));
diff --git a/iocore/net/quic/QUICCryptoStream.h b/iocore/net/quic/QUICCryptoStream.h
index 42f99db..a1331f8 100644
--- a/iocore/net/quic/QUICCryptoStream.h
+++ b/iocore/net/quic/QUICCryptoStream.h
@@ -24,6 +24,7 @@
 #pragma once
 
 #include "QUICStream.h"
+#include "QUICStream_native.h"
 
 /**
  * @brief QUIC Crypto stream
@@ -33,7 +34,7 @@
  * - no flow control
  * - no state (never closed)
  */
-class QUICCryptoStream : public QUICStream
+class QUICCryptoStream : public QUICStreamBase
 {
 public:
   QUICCryptoStream();
@@ -42,7 +43,6 @@
   int state_stream_open(int event, void *data);
 
   const QUICConnectionInfoProvider *info() const;
-  QUICOffset final_offset() const;
   void reset_send_offset();
   void reset_recv_offset();
 
@@ -55,7 +55,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
 private:
   void _on_frame_acked(QUICFrameInformationUPtr &info) override;
diff --git a/iocore/net/quic/QUICFlowController.cc b/iocore/net/quic/QUICFlowController.cc
index 647e4c4..25864b7 100644
--- a/iocore/net/quic/QUICFlowController.cc
+++ b/iocore/net/quic/QUICFlowController.cc
@@ -88,6 +88,18 @@
 }
 
 void
+QUICFlowController::on_frame_acked(QUICFrameInformationUPtr &info)
+{
+  this->_on_frame_acked(info);
+}
+
+void
+QUICFlowController::on_frame_lost(QUICFrameInformationUPtr &info)
+{
+  this->_on_frame_lost(info);
+}
+
+void
 QUICFlowController::set_limit(QUICOffset limit)
 {
   ink_assert(this->_limit == UINT64_MAX || this->_limit == limit);
@@ -110,16 +122,20 @@
  */
 QUICFrame *
 QUICFlowController::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                   uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
+                                   uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                   QUICFrameGenerator *owner)
 {
   QUICFrame *frame = nullptr;
+  if (owner == nullptr) {
+    owner = this;
+  }
 
   if (!this->_is_level_matched(level)) {
     return frame;
   }
 
   if (this->_should_create_frame) {
-    frame = this->_create_frame(buf);
+    frame = this->_create_frame(buf, owner);
     if (frame) {
       if (frame->size() <= maximum_frame_size) {
         this->_should_create_frame                    = false;
@@ -231,25 +247,25 @@
 // QUIC[Remote|Local][Connection|Stream]FlowController
 //
 QUICFrame *
-QUICRemoteConnectionFlowController::_create_frame(uint8_t *buf)
+QUICRemoteConnectionFlowController::_create_frame(uint8_t *buf, QUICFrameGenerator *owner)
 {
-  return QUICFrameFactory::create_data_blocked_frame(buf, this->_offset, this->_issue_frame_id(), this);
+  return QUICFrameFactory::create_data_blocked_frame(buf, this->_offset, this->_issue_frame_id(), owner);
 }
 
 QUICFrame *
-QUICLocalConnectionFlowController::_create_frame(uint8_t *buf)
+QUICLocalConnectionFlowController::_create_frame(uint8_t *buf, QUICFrameGenerator *owner)
 {
-  return QUICFrameFactory::create_max_data_frame(buf, this->_limit, this->_issue_frame_id(), this);
+  return QUICFrameFactory::create_max_data_frame(buf, this->_limit, this->_issue_frame_id(), owner);
 }
 
 QUICFrame *
-QUICRemoteStreamFlowController::_create_frame(uint8_t *buf)
+QUICRemoteStreamFlowController::_create_frame(uint8_t *buf, QUICFrameGenerator *owner)
 {
-  return QUICFrameFactory::create_stream_data_blocked_frame(buf, this->_stream_id, this->_offset, this->_issue_frame_id(), this);
+  return QUICFrameFactory::create_stream_data_blocked_frame(buf, this->_stream_id, this->_offset, this->_issue_frame_id(), owner);
 }
 
 QUICFrame *
-QUICLocalStreamFlowController::_create_frame(uint8_t *buf)
+QUICLocalStreamFlowController::_create_frame(uint8_t *buf, QUICFrameGenerator *owner)
 {
-  return QUICFrameFactory::create_max_stream_data_frame(buf, this->_stream_id, this->_limit, this->_issue_frame_id(), this);
+  return QUICFrameFactory::create_max_stream_data_frame(buf, this->_stream_id, this->_limit, this->_issue_frame_id(), owner);
 }
diff --git a/iocore/net/quic/QUICFlowController.h b/iocore/net/quic/QUICFlowController.h
index 3ff1838..1514288 100644
--- a/iocore/net/quic/QUICFlowController.h
+++ b/iocore/net/quic/QUICFlowController.h
@@ -53,6 +53,9 @@
   virtual int update(QUICOffset offset);
   virtual void forward_limit(QUICOffset limit);
 
+  void on_frame_acked(QUICFrameInformationUPtr &info);
+  void on_frame_lost(QUICFrameInformationUPtr &info);
+
   /**
    * This is only for flow controllers initialized without a limit (== UINT64_MAX).
    * Once a limit is set, it should be updated with forward_limit().
@@ -62,11 +65,11 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
 protected:
   QUICFlowController(uint64_t initial_limit) : _limit(initial_limit) {}
-  virtual QUICFrame *_create_frame(uint8_t *buf) = 0;
+  virtual QUICFrame *_create_frame(uint8_t *buf, QUICFrameGenerator *owner) = 0;
 
   QUICOffset _offset        = 0; //< Largest sent/received offset
   QUICOffset _limit         = 0; //< Maximum amount of data to send/receive
@@ -113,7 +116,7 @@
 {
 public:
   QUICRemoteConnectionFlowController(uint64_t initial_limit) : QUICRemoteFlowController(initial_limit) {}
-  QUICFrame *_create_frame(uint8_t *buf) override;
+  QUICFrame *_create_frame(uint8_t *buf, QUICFrameGenerator *owner) override;
 };
 
 class QUICLocalConnectionFlowController : public QUICLocalFlowController
@@ -123,7 +126,7 @@
     : QUICLocalFlowController(rtt_provider, initial_limit)
   {
   }
-  QUICFrame *_create_frame(uint8_t *buf) override;
+  QUICFrame *_create_frame(uint8_t *buf, QUICFrameGenerator *owner) override;
 };
 
 class QUICRemoteStreamFlowController : public QUICRemoteFlowController
@@ -133,7 +136,7 @@
     : QUICRemoteFlowController(initial_limit), _stream_id(stream_id)
   {
   }
-  QUICFrame *_create_frame(uint8_t *buf) override;
+  QUICFrame *_create_frame(uint8_t *buf, QUICFrameGenerator *owner) override;
 
 private:
   QUICStreamId _stream_id = 0;
@@ -146,7 +149,7 @@
     : QUICLocalFlowController(rtt_provider, initial_limit), _stream_id(stream_id)
   {
   }
-  QUICFrame *_create_frame(uint8_t *buf) override;
+  QUICFrame *_create_frame(uint8_t *buf, QUICFrameGenerator *owner) override;
 
 private:
   QUICStreamId _stream_id = 0;
diff --git a/iocore/net/quic/QUICFrameGenerator.h b/iocore/net/quic/QUICFrameGenerator.h
index f415ff7..c54001e 100644
--- a/iocore/net/quic/QUICFrameGenerator.h
+++ b/iocore/net/quic/QUICFrameGenerator.h
@@ -37,7 +37,8 @@
    * It returns a pointer for the frame if it succeeded, and returns nullptr if it failed.
    */
   virtual QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
-                                    uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num) = 0;
+                                    uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                    QUICFrameGenerator *owner = nullptr) = 0;
 
   void on_frame_acked(QUICFrameId id);
   void on_frame_lost(QUICFrameId id);
@@ -85,7 +86,7 @@
 
   QUICFrame *
   generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                 size_t current_packet_size, uint32_t seq_num) override
+                 size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner = nullptr) override
   {
     this->_seq_num = seq_num;
     return this->_generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size);
diff --git a/iocore/net/quic/QUICFrameRetransmitter.h b/iocore/net/quic/QUICFrameRetransmitter.h
index 64f1f95..fd07565 100644
--- a/iocore/net/quic/QUICFrameRetransmitter.h
+++ b/iocore/net/quic/QUICFrameRetransmitter.h
@@ -31,6 +31,7 @@
 struct QUICFrameInformation {
   QUICFrameType type;
   QUICEncryptionLevel level;
+  QUICStreamId stream_id;
 
   uint8_t data[128] = {};
 };
diff --git a/iocore/net/quic/QUICGlobals.cc b/iocore/net/quic/QUICGlobals.cc
index 6dcd8f0..3cb4ea5 100644
--- a/iocore/net/quic/QUICGlobals.cc
+++ b/iocore/net/quic/QUICGlobals.cc
@@ -53,6 +53,8 @@
 int
 QUIC::ssl_client_new_session(SSL *ssl, SSL_SESSION *session)
 {
+#if HAVE_QUICHE_H
+#else
   QUICTLS *qtls            = static_cast<QUICTLS *>(SSL_get_ex_data(ssl, QUIC::ssl_quic_tls_index));
   const char *session_file = qtls->session_file();
   auto file                = BIO_new_file(session_file, "w");
@@ -64,6 +66,7 @@
 
   PEM_write_bio_SSL_SESSION(file, session);
   BIO_free(file);
+#endif
   return 0;
 }
 
diff --git a/iocore/net/quic/QUICHandshake.cc b/iocore/net/quic/QUICHandshake.cc
index f64f222..74b5e8d 100644
--- a/iocore/net/quic/QUICHandshake.cc
+++ b/iocore/net/quic/QUICHandshake.cc
@@ -259,14 +259,12 @@
   }
 
   // Check if CIDs in TP match with the ones in packets
-  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    uint16_t cid_buf_len;
-    const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
-    QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
-    if (cid_in_tp != this->_initial_source_cid_received) {
-      this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
-      return false;
-    }
+  uint16_t cid_buf_len;
+  const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+  QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+  if (cid_in_tp != this->_initial_source_cid_received) {
+    this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+    return false;
   }
 
   this->_remote_transport_parameters = tp;
@@ -285,23 +283,21 @@
   }
 
   // Check if CIDs in TP match with the ones in packets
-  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    uint16_t cid_buf_len;
-    const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+  uint16_t cid_buf_len;
+  const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+  QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+  if (cid_in_tp != this->_initial_source_cid_received) {
+    this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+    return false;
+  }
+
+  if (!this->_retry_source_cid_received.is_zero()) {
+    cid_buf = tp->getAsBytes(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, cid_buf_len);
     QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
-    if (cid_in_tp != this->_initial_source_cid_received) {
+    if (cid_in_tp != this->_retry_source_cid_received) {
       this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
       return false;
     }
-
-    if (!this->_retry_source_cid_received.is_zero()) {
-      cid_buf = tp->getAsBytes(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, cid_buf_len);
-      QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
-      if (cid_in_tp != this->_retry_source_cid_received) {
-        this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
-        return false;
-      }
-    }
   }
 
   this->_remote_transport_parameters = tp;
@@ -399,14 +395,14 @@
 
 QUICFrame *
 QUICHandshake::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                              size_t current_packet_size, uint32_t seq_num)
+                              size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner)
 {
   QUICFrame *frame = nullptr;
 
   if (this->_is_level_matched(level)) {
     // CRYPTO
     frame = this->_crypto_streams[static_cast<int>(level)].generate_frame(buf, level, connection_credit, maximum_frame_size,
-                                                                          current_packet_size, seq_num);
+                                                                          current_packet_size, seq_num, nullptr);
     if (frame) {
       return frame;
     }
@@ -439,15 +435,11 @@
     tp->set(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, this->_qc->retry_source_connection_id(),
             this->_qc->retry_source_connection_id().length());
   } else {
-    if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-      tp->set(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, this->_qc->original_connection_id(),
-              this->_qc->original_connection_id().length());
-    }
+    tp->set(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, this->_qc->original_connection_id(),
+            this->_qc->original_connection_id().length());
   }
-  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    tp->set(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, this->_qc->initial_source_connection_id(),
-            this->_qc->initial_source_connection_id().length());
-  }
+  tp->set(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, this->_qc->initial_source_connection_id(),
+          this->_qc->initial_source_connection_id().length());
 
   // MAYs
   if (tp_config.initial_max_data() != 0) {
diff --git a/iocore/net/quic/QUICHandshake.h b/iocore/net/quic/QUICHandshake.h
index 4f55985..923f0aa 100644
--- a/iocore/net/quic/QUICHandshake.h
+++ b/iocore/net/quic/QUICHandshake.h
@@ -55,7 +55,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
   // for client side
   QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_factory, bool vn_exercise_enabled);
diff --git a/iocore/net/quic/QUICKeyGenerator.cc b/iocore/net/quic/QUICKeyGenerator.cc
index 09ed9ed..50c2fa5 100644
--- a/iocore/net/quic/QUICKeyGenerator.cc
+++ b/iocore/net/quic/QUICKeyGenerator.cc
@@ -34,10 +34,10 @@
 using namespace std::literals;
 
 constexpr static uint8_t QUIC_VERSION_1_SALT[] = {
-  0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
+  0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
 };
-constexpr static uint8_t QUIC_VERSION_1_D27_SALT[] = {
-  0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
+constexpr static uint8_t QUIC_VERSION_1_D29_SALT[] = {
+  0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
 };
 constexpr static std::string_view LABEL_FOR_CLIENT_INITIAL_SECRET("client in"sv);
 constexpr static std::string_view LABEL_FOR_SERVER_INITIAL_SECRET("server in"sv);
@@ -117,13 +117,13 @@
   // TODO: do not extract initial secret twice
   QUICTypeUtil::write_QUICConnectionId(cid, client_connection_id, &cid_len);
   switch (version) {
-  case 0xff00001d: // Draft-29
+  case 0x00000001: // Version 1
     salt     = QUIC_VERSION_1_SALT;
     salt_len = sizeof(QUIC_VERSION_1_SALT);
     break;
-  case 0xff00001b: // Draft-27
-    salt     = QUIC_VERSION_1_D27_SALT;
-    salt_len = sizeof(QUIC_VERSION_1_D27_SALT);
+  case 0xff00001d: // Draft-29
+    salt     = QUIC_VERSION_1_D29_SALT;
+    salt_len = sizeof(QUIC_VERSION_1_D29_SALT);
     break;
   default:
     salt     = QUIC_VERSION_1_SALT;
diff --git a/iocore/net/quic/QUICPathValidator.cc b/iocore/net/quic/QUICPathValidator.cc
index e12022c..20abbb9 100644
--- a/iocore/net/quic/QUICPathValidator.cc
+++ b/iocore/net/quic/QUICPathValidator.cc
@@ -200,7 +200,8 @@
  */
 QUICFrame *
 QUICPathValidator::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                  uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
+                                  uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                  QUICFrameGenerator *owner)
 {
   QUICFrame *frame = nullptr;
 
diff --git a/iocore/net/quic/QUICPathValidator.h b/iocore/net/quic/QUICPathValidator.h
index 96f5f4f..7f31196 100644
--- a/iocore/net/quic/QUICPathValidator.h
+++ b/iocore/net/quic/QUICPathValidator.h
@@ -48,7 +48,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
 private:
   enum class ValidationState : int {
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.cc b/iocore/net/quic/QUICRetryIntegrityTag.cc
index 117dff6..e8988ba 100644
--- a/iocore/net/quic/QUICRetryIntegrityTag.cc
+++ b/iocore/net/quic/QUICRetryIntegrityTag.cc
@@ -41,13 +41,13 @@
   const uint8_t *key;
   const uint8_t *nonce;
   switch (version) {
-  case 0xff00001d: // Draft-29
+  case 0x00000001: // Version 1
     key   = KEY_FOR_RETRY_INTEGRITY_TAG;
     nonce = NONCE_FOR_RETRY_INTEGRITY_TAG;
     break;
-  case 0xff00001b: // Draft-27
-    key   = KEY_FOR_RETRY_INTEGRITY_TAG_D27;
-    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG_D27;
+  case 0xff00001d: // Draft-29
+    key   = KEY_FOR_RETRY_INTEGRITY_TAG_D29;
+    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG_D29;
     break;
   default:
     key   = KEY_FOR_RETRY_INTEGRITY_TAG;
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.h b/iocore/net/quic/QUICRetryIntegrityTag.h
index 081bd59..3649fff 100644
--- a/iocore/net/quic/QUICRetryIntegrityTag.h
+++ b/iocore/net/quic/QUICRetryIntegrityTag.h
@@ -33,13 +33,14 @@
                       Ptr<IOBufferBlock> payload);
 
 private:
-  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG[]   = {0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0,
-                                                            0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1};
-  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG[] = {0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21,
-                                                              0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c};
-  // For draft 27
-  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG_D27[]   = {0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, 0x33, 0xc8,
-                                                                0x41, 0xe4, 0x04, 0x3d, 0xf2, 0x7d, 0x44, 0x30};
-  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG_D27[] = {0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13,
-                                                                  0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75};
+  // For version 1
+  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG[]   = {0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a,
+                                                            0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e};
+  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG[] = {0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63,
+                                                              0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb};
+  // For draft 29
+  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG_D29[]   = {0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0,
+                                                                0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1};
+  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG_D29[] = {0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21,
+                                                                  0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c};
 };
diff --git a/iocore/net/quic/QUICStream.cc b/iocore/net/quic/QUICStream.cc
index 932d1c1..8753f9d 100644
--- a/iocore/net/quic/QUICStream.cc
+++ b/iocore/net/quic/QUICStream.cc
@@ -37,171 +37,27 @@
   return this->_id;
 }
 
+const QUICConnectionInfoProvider *
+QUICStream::connection_info()
+{
+  return this->_connection_info;
+}
+
 QUICStreamDirection
 QUICStream::direction() const
 {
   return QUICTypeUtil::detect_stream_direction(this->_id, this->_connection_info->direction());
 }
 
-const QUICConnectionInfoProvider *
-QUICStream::connection_info() const
-{
-  return this->_connection_info;
-}
-
 bool
 QUICStream::is_bidirectional() const
 {
   return ((this->_id & 0x03) < 0x02);
 }
 
-QUICOffset
-QUICStream::final_offset() const
-{
-  // TODO Return final offset
-  return 0;
-}
-
 void
 QUICStream::set_io_adapter(QUICStreamAdapter *adapter)
 {
   this->_adapter = adapter;
   this->_on_adapter_updated();
 }
-
-QUICOffset
-QUICStream::reordered_bytes() const
-{
-  return this->_reordered_bytes;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStreamFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICMaxStreamDataFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStreamDataBlockedFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStopSendingFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICRstStreamFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICCryptoFrame &frame)
-{
-  return nullptr;
-}
-
-void
-QUICStream::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame)
-{
-  QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                    = frame.type();
-  info->level                   = level;
-  StreamFrameInfo *frame_info   = reinterpret_cast<StreamFrameInfo *>(info->data);
-  frame_info->stream_id         = frame.stream_id();
-  frame_info->offset            = frame.offset();
-  frame_info->has_fin           = frame.has_fin_flag();
-  frame_info->block             = frame.data();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame)
-{
-  QUICFrameInformationUPtr info  = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                     = frame.type();
-  info->level                    = level;
-  RstStreamFrameInfo *frame_info = reinterpret_cast<RstStreamFrameInfo *>(info->data);
-  frame_info->error_code         = frame.error_code();
-  frame_info->final_offset       = frame.final_offset();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame)
-{
-  QUICFrameInformationUPtr info    = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                       = frame.type();
-  info->level                      = level;
-  StopSendingFrameInfo *frame_info = reinterpret_cast<StopSendingFrameInfo *>(info->data);
-  frame_info->error_code           = frame.error_code();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame)
-{
-  QUICFrameInformationUPtr info      = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                         = QUICFrameType::CRYPTO;
-  info->level                        = level;
-  CryptoFrameInfo *crypto_frame_info = reinterpret_cast<CryptoFrameInfo *>(info->data);
-  crypto_frame_info->offset          = frame.offset();
-  crypto_frame_info->block           = frame.data();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::set_state_listener(QUICStreamStateListener *listener)
-{
-  this->_state_listener = listener;
-}
-
-void
-QUICStream::_notify_state_change()
-{
-  if (this->_state_listener) {
-    // TODO Check own state and call an appropriate callback function
-  }
-}
-
-void
-QUICStream::reset(QUICStreamErrorUPtr error)
-{
-}
-
-void
-QUICStream::stop_sending(QUICStreamErrorUPtr error)
-{
-}
-
-QUICOffset
-QUICStream::largest_offset_received() const
-{
-  return 0;
-}
-
-QUICOffset
-QUICStream::largest_offset_sent() const
-{
-  return 0;
-}
-
-void
-QUICStream::on_eos()
-{
-}
-
-void
-QUICStream::on_read()
-{
-}
diff --git a/iocore/net/quic/QUICStream.h b/iocore/net/quic/QUICStream.h
index d6cdf06..81aea2a 100644
--- a/iocore/net/quic/QUICStream.h
+++ b/iocore/net/quic/QUICStream.h
@@ -44,7 +44,7 @@
  * @brief QUIC Stream
  * TODO: This is similar to Http2Stream. Need to think some integration.
  */
-class QUICStream : public QUICFrameGenerator, public QUICFrameRetransmitter
+class QUICStream
 {
 public:
   QUICStream() {}
@@ -52,10 +52,20 @@
   virtual ~QUICStream();
 
   QUICStreamId id() const;
+  const QUICConnectionInfoProvider *connection_info();
   QUICStreamDirection direction() const;
-  const QUICConnectionInfoProvider *connection_info() const;
   bool is_bidirectional() const;
-  QUICOffset final_offset() const;
+
+  virtual QUICOffset final_offset() const = 0;
+
+  virtual void stop_sending(QUICStreamErrorUPtr error) = 0;
+  virtual void reset(QUICStreamErrorUPtr error)        = 0;
+
+  /*
+   * QUICApplication need to call one of these functions when it process VC_EVENT_*
+   */
+  virtual void on_read() = 0;
+  virtual void on_eos()  = 0;
 
   /**
    * Set an adapter to read/write data from/to this stream
@@ -64,48 +74,12 @@
    * to access data in the  way the applications wants.
    */
   void set_io_adapter(QUICStreamAdapter *adapter);
-
-  /*
-   * QUICApplication need to call one of these functions when it process VC_EVENT_*
-   */
-  virtual void on_read();
-  virtual void on_eos();
-
-  virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICCryptoFrame &frame);
-
-  QUICOffset reordered_bytes() const;
-  virtual QUICOffset largest_offset_received() const;
-  virtual QUICOffset largest_offset_sent() const;
-
-  virtual void stop_sending(QUICStreamErrorUPtr error);
-  virtual void reset(QUICStreamErrorUPtr error);
-
-  void set_state_listener(QUICStreamStateListener *listener);
-
-  LINK(QUICStream, link);
+  virtual void _on_adapter_updated(){};
 
 protected:
   QUICConnectionInfoProvider *_connection_info = nullptr;
   QUICStreamId _id                             = 0;
-  QUICOffset _send_offset                      = 0;
-  QUICOffset _reordered_bytes                  = 0;
-
-  QUICStreamAdapter *_adapter              = nullptr;
-  QUICStreamStateListener *_state_listener = nullptr;
-
-  virtual void _on_adapter_updated(){};
-
-  void _notify_state_change();
-
-  void _records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame);
-  void _records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame);
-  void _records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame);
-  void _records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame);
+  QUICStreamAdapter *_adapter                  = nullptr;
 };
 
 class QUICStreamStateListener
diff --git a/iocore/net/quic/QUICStreamBase.cc b/iocore/net/quic/QUICStreamBase.cc
new file mode 100644
index 0000000..a2d98a2
--- /dev/null
+++ b/iocore/net/quic/QUICStreamBase.cc
@@ -0,0 +1,185 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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 "QUICStream.h"
+#include "QUICStream_native.h"
+
+QUICOffset
+QUICStreamBase::final_offset() const
+{
+  // TODO Return final offset
+  return 0;
+}
+
+QUICOffset
+QUICStreamBase::reordered_bytes() const
+{
+  return this->_reordered_bytes;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamBase::recv(const QUICStreamFrame &frame)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamBase::recv(const QUICMaxStreamDataFrame &frame)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamBase::recv(const QUICStreamDataBlockedFrame &frame)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamBase::recv(const QUICStopSendingFrame &frame)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamBase::recv(const QUICRstStreamFrame &frame)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamBase::recv(const QUICCryptoFrame &frame)
+{
+  return nullptr;
+}
+
+void
+QUICStreamBase::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame)
+{
+  QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
+  info->type                    = frame.type();
+  info->level                   = level;
+  info->stream_id               = this->id();
+  StreamFrameInfo *frame_info   = reinterpret_cast<StreamFrameInfo *>(info->data);
+  frame_info->stream_id         = frame.stream_id();
+  frame_info->offset            = frame.offset();
+  frame_info->has_fin           = frame.has_fin_flag();
+  frame_info->block             = frame.data();
+  this->_records_frame(frame.id(), std::move(info));
+}
+
+void
+QUICStreamBase::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame)
+{
+  QUICFrameInformationUPtr info  = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
+  info->type                     = frame.type();
+  info->level                    = level;
+  info->stream_id                = this->id();
+  RstStreamFrameInfo *frame_info = reinterpret_cast<RstStreamFrameInfo *>(info->data);
+  frame_info->error_code         = frame.error_code();
+  frame_info->final_offset       = frame.final_offset();
+  this->_records_frame(frame.id(), std::move(info));
+}
+
+void
+QUICStreamBase::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame)
+{
+  QUICFrameInformationUPtr info    = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
+  info->type                       = frame.type();
+  info->level                      = level;
+  info->stream_id                  = this->id();
+  StopSendingFrameInfo *frame_info = reinterpret_cast<StopSendingFrameInfo *>(info->data);
+  frame_info->error_code           = frame.error_code();
+  this->_records_frame(frame.id(), std::move(info));
+}
+
+void
+QUICStreamBase::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame)
+{
+  QUICFrameInformationUPtr info      = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
+  info->type                         = QUICFrameType::CRYPTO;
+  info->level                        = level;
+  info->stream_id                    = this->id();
+  CryptoFrameInfo *crypto_frame_info = reinterpret_cast<CryptoFrameInfo *>(info->data);
+  crypto_frame_info->offset          = frame.offset();
+  crypto_frame_info->block           = frame.data();
+  this->_records_frame(frame.id(), std::move(info));
+}
+
+void
+QUICStreamBase::set_state_listener(QUICStreamStateListener *listener)
+{
+  this->_state_listener = listener;
+}
+
+void
+QUICStreamBase::_notify_state_change()
+{
+  if (this->_state_listener) {
+    // TODO Check own state and call an appropriate callback function
+  }
+}
+
+void
+QUICStreamBase::reset(QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamBase::stop_sending(QUICStreamErrorUPtr error)
+{
+}
+
+QUICOffset
+QUICStreamBase::largest_offset_received() const
+{
+  return 0;
+}
+
+QUICOffset
+QUICStreamBase::largest_offset_sent() const
+{
+  return 0;
+}
+
+void
+QUICStreamBase::on_eos()
+{
+}
+
+void
+QUICStreamBase::on_read()
+{
+}
+
+void
+QUICStreamBase::on_frame_acked(QUICFrameInformationUPtr &info)
+{
+  this->_on_frame_acked(info);
+}
+
+void
+QUICStreamBase::on_frame_lost(QUICFrameInformationUPtr &info)
+{
+  this->_on_frame_lost(info);
+}
diff --git a/iocore/net/quic/QUICStreamFactory.cc b/iocore/net/quic/QUICStreamFactory.cc
index 1f09c75..398c637 100644
--- a/iocore/net/quic/QUICStreamFactory.cc
+++ b/iocore/net/quic/QUICStreamFactory.cc
@@ -26,10 +26,10 @@
 #include "QUICUnidirectionalStream.h"
 #include "QUICStreamFactory.h"
 
-QUICStream *
+QUICStreamBase *
 QUICStreamFactory::create(QUICStreamId sid, uint64_t local_max_stream_data, uint64_t remote_max_stream_data)
 {
-  QUICStream *stream = nullptr;
+  QUICStreamBase *stream = nullptr;
   switch (QUICTypeUtil::detect_stream_direction(sid, this->_info->direction())) {
   case QUICStreamDirection::BIDIRECTIONAL:
     stream = new QUICBidirectionalStream(this->_rtt_provider, this->_info, sid, local_max_stream_data, remote_max_stream_data);
diff --git a/iocore/net/quic/QUICStreamFactory.h b/iocore/net/quic/QUICStreamFactory.h
index 43690ed..9898f5a 100644
--- a/iocore/net/quic/QUICStreamFactory.h
+++ b/iocore/net/quic/QUICStreamFactory.h
@@ -35,7 +35,7 @@
   ~QUICStreamFactory() {}
 
   // create a bidistream, send only stream or receive only stream
-  QUICStream *create(QUICStreamId sid, uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
+  QUICStreamBase *create(QUICStreamId sid, uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
 
   // delete stream by stream type
   void delete_stream(QUICStream *stream);
diff --git a/iocore/net/quic/QUICStreamManager.cc b/iocore/net/quic/QUICStreamManager.cc
index 2338321..35d9630 100644
--- a/iocore/net/quic/QUICStreamManager.cc
+++ b/iocore/net/quic/QUICStreamManager.cc
@@ -26,485 +26,12 @@
 #include "QUICApplication.h"
 #include "QUICTransportParameters.h"
 
-static constexpr char tag[]                     = "quic_stream_manager";
-static constexpr QUICStreamId QUIC_STREAM_TYPES = 4;
+QUICStreamManager::QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map) : _context(context), _app_map(app_map) {}
 
-QUICStreamManager::QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map)
-  : _stream_factory(context->rtt_provider(), context->connection_info()), _context(context), _app_map(app_map)
-{
-  if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-    this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::CLIENT_BIDI);
-    this->_next_stream_id_uni  = static_cast<uint32_t>(QUICStreamType::CLIENT_UNI);
-  } else {
-    this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::SERVER_BIDI);
-    this->_next_stream_id_uni  = static_cast<uint32_t>(QUICStreamType::SERVER_UNI);
-  }
-}
-
-QUICStreamManager::~QUICStreamManager()
-{
-  for (auto stream = stream_list.pop(); stream != nullptr; stream = stream_list.pop()) {
-    _stream_factory.delete_stream(stream);
-  }
-}
-
-std::vector<QUICFrameType>
-QUICStreamManager::interests()
-{
-  return {
-    QUICFrameType::STREAM,          QUICFrameType::RESET_STREAM, QUICFrameType::STOP_SENDING,
-    QUICFrameType::MAX_STREAM_DATA, QUICFrameType::MAX_STREAMS,
-  };
-}
-
-void
-QUICStreamManager::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                            const std::shared_ptr<const QUICTransportParameters> &remote_tp)
-{
-  this->_local_tp  = local_tp;
-  this->_remote_tp = remote_tp;
-
-  if (this->_local_tp) {
-    this->_local_max_streams_bidi = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI);
-    this->_local_max_streams_uni  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI);
-  }
-  if (this->_remote_tp) {
-    this->_remote_max_streams_bidi = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI);
-    this->_remote_max_streams_uni  = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI);
-  }
-}
-
-void
-QUICStreamManager::set_max_streams_bidi(uint64_t max_streams)
-{
-  if (this->_local_max_streams_bidi <= max_streams) {
-    this->_local_max_streams_bidi = max_streams;
-  }
-}
-
-void
-QUICStreamManager::set_max_streams_uni(uint64_t max_streams)
-{
-  if (this->_local_max_streams_uni <= max_streams) {
-    this->_local_max_streams_uni = max_streams;
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::create_stream(QUICStreamId stream_id)
-{
-  // TODO: check stream_id
-  QUICConnectionErrorUPtr error = nullptr;
-  QUICStream *stream            = this->_find_or_create_stream(stream_id);
-  if (!stream) {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-
-  QUICApplication *application = this->_app_map->get(stream_id);
-  application->on_new_stream(*stream);
-
-  return error;
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::create_uni_stream(QUICStreamId &new_stream_id)
-{
-  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_uni);
-  if (error == nullptr) {
-    new_stream_id = this->_next_stream_id_uni;
-    this->_next_stream_id_uni += QUIC_STREAM_TYPES;
-  }
-
-  return error;
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::create_bidi_stream(QUICStreamId &new_stream_id)
-{
-  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_bidi);
-  if (error == nullptr) {
-    new_stream_id = this->_next_stream_id_bidi;
-    this->_next_stream_id_bidi += QUIC_STREAM_TYPES;
-  }
-
-  return error;
-}
-
-void
-QUICStreamManager::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
-{
-  auto stream = this->_find_stream(stream_id);
-  stream->reset(std::move(error));
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
-{
-  QUICConnectionErrorUPtr error = nullptr;
-
-  switch (frame.type()) {
-  case QUICFrameType::MAX_STREAM_DATA:
-    error = this->_handle_frame(static_cast<const QUICMaxStreamDataFrame &>(frame));
-    break;
-  case QUICFrameType::STREAM_DATA_BLOCKED:
-    // STREAM_DATA_BLOCKED frame is for debugging. Just propagate to streams
-    error = this->_handle_frame(static_cast<const QUICStreamDataBlockedFrame &>(frame));
-    break;
-  case QUICFrameType::STREAM:
-    error = this->_handle_frame(static_cast<const QUICStreamFrame &>(frame));
-    break;
-  case QUICFrameType::STOP_SENDING:
-    error = this->_handle_frame(static_cast<const QUICStopSendingFrame &>(frame));
-    break;
-  case QUICFrameType::RESET_STREAM:
-    error = this->_handle_frame(static_cast<const QUICRstStreamFrame &>(frame));
-    break;
-  case QUICFrameType::MAX_STREAMS:
-    error = this->_handle_frame(static_cast<const QUICMaxStreamsFrame &>(frame));
-    break;
-  default:
-    Debug(tag, "Unexpected frame type: %02x", static_cast<unsigned int>(frame.type()));
-    ink_assert(false);
-    break;
-  }
-
-  return error;
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICMaxStreamDataFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStreamDataBlockedFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStreamFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICRstStreamFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStopSendingFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICMaxStreamsFrame &frame)
-{
-  QUICStreamType type = QUICTypeUtil::detect_stream_type(frame.maximum_streams());
-  if (type == QUICStreamType::SERVER_BIDI || type == QUICStreamType::CLIENT_BIDI) {
-    this->_remote_max_streams_bidi = frame.maximum_streams();
-  } else {
-    this->_remote_max_streams_uni = frame.maximum_streams();
-  }
-  return nullptr;
-}
-
-QUICStream *
-QUICStreamManager::_find_stream(QUICStreamId id)
-{
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    if (s->id() == id) {
-      return s;
-    }
-  }
-  return nullptr;
-}
-
-QUICStream *
-QUICStreamManager::_find_or_create_stream(QUICStreamId stream_id)
-{
-  QUICStream *stream = this->_find_stream(stream_id);
-  if (!stream) {
-    if (!this->_local_tp) {
-      return nullptr;
-    }
-
-    ink_assert(this->_local_tp);
-    ink_assert(this->_remote_tp);
-
-    uint64_t local_max_stream_data  = 0;
-    uint64_t remote_max_stream_data = 0;
-    uint64_t nth_stream             = this->_stream_id_to_nth_stream(stream_id);
-
-    switch (QUICTypeUtil::detect_stream_type(stream_id)) {
-    case QUICStreamType::CLIENT_BIDI:
-      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        // client
-        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-      } else {
-        // server
-        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-      }
-
-      break;
-    case QUICStreamType::CLIENT_UNI:
-      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        // client
-        if (this->_remote_max_streams_uni == 0 || nth_stream > this->_remote_max_streams_uni) {
-          return nullptr;
-        }
-      } else {
-        // server
-        if (this->_local_max_streams_uni == 0 || nth_stream > this->_local_max_streams_uni) {
-          return nullptr;
-        }
-      }
-
-      local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-      remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-
-      break;
-    case QUICStreamType::SERVER_BIDI:
-      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        // client
-        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-      } else {
-        // server
-        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-      }
-      break;
-    case QUICStreamType::SERVER_UNI:
-      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        if (this->_local_max_streams_uni == 0 || nth_stream > this->_local_max_streams_uni) {
-          return nullptr;
-        }
-      } else {
-        if (this->_remote_max_streams_uni == 0 || nth_stream > this->_remote_max_streams_uni) {
-          return nullptr;
-        }
-      }
-
-      local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-      remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-
-      break;
-    default:
-      ink_release_assert(false);
-      break;
-    }
-
-    stream = this->_stream_factory.create(stream_id, local_max_stream_data, remote_max_stream_data);
-    ink_assert(stream != nullptr);
-    stream->set_state_listener(this);
-    this->stream_list.push(stream);
-
-    QUICApplication *application = this->_app_map->get(stream_id);
-    application->on_new_stream(*stream);
-  }
-
-  return stream;
-}
-
-uint64_t
-QUICStreamManager::total_reordered_bytes() const
-{
-  uint64_t total_bytes = 0;
-
-  // FIXME Iterating all (open + closed) streams is expensive
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    total_bytes += s->reordered_bytes();
-  }
-  return total_bytes;
-}
-
-uint64_t
-QUICStreamManager::total_offset_received() const
-{
-  uint64_t total_offset_received = 0;
-
-  // FIXME Iterating all (open + closed) streams is expensive
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    total_offset_received += s->largest_offset_received();
-  }
-  return total_offset_received;
-}
-
-uint64_t
-QUICStreamManager::total_offset_sent() const
-{
-  return this->_total_offset_sent;
-}
-
-void
-QUICStreamManager::_add_total_offset_sent(uint32_t sent_byte)
-{
-  // FIXME: use atomic increment
-  this->_total_offset_sent += sent_byte;
-}
-
-uint32_t
-QUICStreamManager::stream_count() const
-{
-  uint32_t count = 0;
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    ++count;
-  }
-  return count;
-}
+QUICStreamManager::~QUICStreamManager() {}
 
 void
 QUICStreamManager::set_default_application(QUICApplication *app)
 {
   this->_app_map->set_default(app);
 }
-
-bool
-QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
-{
-  if (!this->_is_level_matched(level)) {
-    return false;
-  }
-
-  // workaround fix until support 0-RTT on client
-  if (level == QUICEncryptionLevel::ZERO_RTT) {
-    return false;
-  }
-
-  // Don't send DATA frames if current path is not validated
-  if (!this->_context->path_manager()->get_verified_path().remote_ep().isValid()) {
-    return false;
-  }
-
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    if (s->will_generate_frame(level, current_packet_size, ack_eliciting, seq_num)) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-QUICFrame *
-QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                  size_t current_packet_size, uint32_t seq_num)
-{
-  QUICFrame *frame = nullptr;
-
-  if (!this->_is_level_matched(level)) {
-    return frame;
-  }
-
-  // workaround fix until support 0-RTT on client
-  if (level == QUICEncryptionLevel::ZERO_RTT) {
-    return frame;
-  }
-
-  // FIXME We should pick a stream based on priority
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size, seq_num);
-    if (frame) {
-      break;
-    }
-  }
-
-  if (frame != nullptr && frame->type() == QUICFrameType::STREAM) {
-    this->_add_total_offset_sent(static_cast<QUICStreamFrame *>(frame)->data_length());
-  }
-
-  return frame;
-}
-
-void
-QUICStreamManager::on_stream_state_close(const QUICStream *stream)
-{
-  auto direction = this->_context->connection_info()->direction();
-  switch (QUICTypeUtil::detect_stream_type(stream->id())) {
-  case QUICStreamType::SERVER_BIDI:
-    if (direction == NET_VCONNECTION_OUT) {
-      this->_local_max_streams_bidi += 1;
-    }
-    break;
-  case QUICStreamType::SERVER_UNI:
-    if (direction == NET_VCONNECTION_OUT) {
-      this->_local_max_streams_uni += 1;
-    }
-    break;
-  case QUICStreamType::CLIENT_BIDI:
-    if (direction == NET_VCONNECTION_IN) {
-      this->_local_max_streams_bidi += 1;
-    }
-    break;
-  case QUICStreamType::CLIENT_UNI:
-    if (direction == NET_VCONNECTION_IN) {
-      this->_local_max_streams_uni += 1;
-    }
-    break;
-  }
-}
-
-bool
-QUICStreamManager::_is_level_matched(QUICEncryptionLevel level)
-{
-  for (auto l : this->_encryption_level_filter) {
-    if (l == level) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-uint64_t
-QUICStreamManager::_stream_id_to_nth_stream(QUICStreamId stream_id)
-{
-  return (stream_id / 4) + 1;
-}
diff --git a/iocore/net/quic/QUICStreamManager.h b/iocore/net/quic/QUICStreamManager.h
index a52a375..e7a3bfd 100644
--- a/iocore/net/quic/QUICStreamManager.h
+++ b/iocore/net/quic/QUICStreamManager.h
@@ -24,85 +24,37 @@
 #pragma once
 
 #include "QUICTypes.h"
-#include "QUICBidirectionalStream.h"
-#include "QUICUnidirectionalStream.h"
 #include "QUICApplicationMap.h"
-#include "QUICFrameHandler.h"
-#include "QUICFrame.h"
-#include "QUICStreamFactory.h"
-#include "QUICLossDetector.h"
-#include "QUICPathManager.h"
 #include "QUICContext.h"
 
 class QUICTransportParameters;
 
-class QUICStreamManager : public QUICFrameHandler, public QUICFrameGenerator, public QUICStreamStateListener
+class QUICStreamManager : public QUICStreamStateListener
 {
 public:
   QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map);
-  ~QUICStreamManager();
+  virtual ~QUICStreamManager();
 
-  void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                const std::shared_ptr<const QUICTransportParameters> &remote_tp);
-  void set_max_streams_bidi(uint64_t max_streams);
-  void set_max_streams_uni(uint64_t max_streams);
-  uint64_t total_reordered_bytes() const;
-  uint64_t total_offset_received() const;
-  uint64_t total_offset_sent() const;
+  virtual void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                        const std::shared_ptr<const QUICTransportParameters> &remote_tp) = 0;
+  virtual void set_max_streams_bidi(uint64_t max_streams)                                                = 0;
+  virtual void set_max_streams_uni(uint64_t max_streams)                                                 = 0;
+  virtual uint64_t total_reordered_bytes() const                                                         = 0;
+  virtual uint64_t total_offset_received() const                                                         = 0;
+  virtual uint64_t total_offset_sent() const                                                             = 0;
 
-  uint32_t stream_count() const;
-  QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id);
-  QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id);
-  QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id);
-  void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error);
+  virtual uint32_t stream_count() const                   = 0;
+  virtual QUICStream *find_stream(QUICStreamId stream_id) = 0;
+
+  virtual QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id)           = 0;
+  virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id)  = 0;
+  virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id) = 0;
+  virtual QUICConnectionErrorUPtr delete_stream(QUICStreamId &new_stream_id)      = 0;
+  virtual void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)    = 0;
 
   void set_default_application(QUICApplication *app);
 
-  DLL<QUICStream> stream_list;
-
-  // QUICFrameHandler
-  virtual std::vector<QUICFrameType> interests() override;
-  virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
-
-  // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t timestamp) override;
-  QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t timestamp) override;
-
-  // QUICStreamStateListener
-  void on_stream_state_close(const QUICStream *stream) override;
-
 protected:
-  virtual bool _is_level_matched(QUICEncryptionLevel level) override;
-
-private:
-  QUICStream *_find_stream(QUICStreamId id);
-  QUICStream *_find_or_create_stream(QUICStreamId stream_id);
-  void _add_total_offset_sent(uint32_t sent_byte);
-  QUICConnectionErrorUPtr _handle_frame(const QUICStreamFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICRstStreamFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICStopSendingFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamDataFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICStreamDataBlockedFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamsFrame &frame);
-
-  QUICStreamFactory _stream_factory;
-
-  QUICContext *_context                                       = nullptr;
-  QUICApplicationMap *_app_map                                = nullptr;
-  std::shared_ptr<const QUICTransportParameters> _local_tp    = nullptr;
-  std::shared_ptr<const QUICTransportParameters> _remote_tp   = nullptr;
-  QUICStreamId _local_max_streams_bidi                        = 0;
-  QUICStreamId _local_max_streams_uni                         = 0;
-  QUICStreamId _remote_max_streams_bidi                       = 0;
-  QUICStreamId _remote_max_streams_uni                        = 0;
-  QUICStreamId _next_stream_id_uni                            = 0;
-  QUICStreamId _next_stream_id_bidi                           = 0;
-  uint64_t _total_offset_sent                                 = 0;
-  std::array<QUICEncryptionLevel, 2> _encryption_level_filter = {
-    QUICEncryptionLevel::ZERO_RTT,
-    QUICEncryptionLevel::ONE_RTT,
-  };
-
-  uint64_t _stream_id_to_nth_stream(QUICStreamId stream_id);
+  QUICContext *_context        = nullptr;
+  QUICApplicationMap *_app_map = nullptr;
 };
diff --git a/iocore/net/quic/QUICStreamManager_native.cc b/iocore/net/quic/QUICStreamManager_native.cc
new file mode 100644
index 0000000..7ecf64b
--- /dev/null
+++ b/iocore/net/quic/QUICStreamManager_native.cc
@@ -0,0 +1,540 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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 "QUICStreamManager_native.h"
+
+static constexpr char tag[]                     = "quic_stream_manager";
+static constexpr QUICStreamId QUIC_STREAM_TYPES = 4;
+
+QUICStreamManagerImpl::QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map)
+  : QUICStreamManager(context, app_map), _stream_factory(context->rtt_provider(), context->connection_info())
+{
+  if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
+    this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::CLIENT_BIDI);
+    this->_next_stream_id_uni  = static_cast<uint32_t>(QUICStreamType::CLIENT_UNI);
+  } else {
+    this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::SERVER_BIDI);
+    this->_next_stream_id_uni  = static_cast<uint32_t>(QUICStreamType::SERVER_UNI);
+  }
+}
+
+QUICStreamManagerImpl::~QUICStreamManagerImpl()
+{
+  for (auto stream = stream_list.pop(); stream != nullptr; stream = stream_list.pop()) {
+    _stream_factory.delete_stream(stream);
+  }
+}
+
+void
+QUICStreamManagerImpl::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                                const std::shared_ptr<const QUICTransportParameters> &remote_tp)
+{
+  this->_local_tp  = local_tp;
+  this->_remote_tp = remote_tp;
+
+  if (this->_local_tp) {
+    this->_local_max_streams_bidi = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI);
+    this->_local_max_streams_uni  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI);
+  }
+  if (this->_remote_tp) {
+    this->_remote_max_streams_bidi = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI);
+    this->_remote_max_streams_uni  = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI);
+  }
+}
+
+void
+QUICStreamManagerImpl::set_max_streams_bidi(uint64_t max_streams)
+{
+  if (this->_local_max_streams_bidi <= max_streams) {
+    this->_local_max_streams_bidi = max_streams;
+  }
+}
+
+void
+QUICStreamManagerImpl::set_max_streams_uni(uint64_t max_streams)
+{
+  if (this->_local_max_streams_uni <= max_streams) {
+    this->_local_max_streams_uni = max_streams;
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_stream(QUICStreamId stream_id)
+{
+  // TODO: check stream_id
+  QUICConnectionErrorUPtr error = nullptr;
+  QUICStream *stream            = this->_find_or_create_stream(stream_id);
+  if (!stream) {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+
+  QUICApplication *application = this->_app_map->get(stream_id);
+  application->on_new_stream(*stream);
+
+  return error;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_uni_stream(QUICStreamId &new_stream_id)
+{
+  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_uni);
+  if (error == nullptr) {
+    new_stream_id = this->_next_stream_id_uni;
+    this->_next_stream_id_uni += QUIC_STREAM_TYPES;
+  }
+
+  return error;
+}
+
+void
+QUICStreamManagerImpl::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
+{
+  auto stream = this->_find_stream(stream_id);
+  stream->reset(std::move(error));
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_bidi_stream(QUICStreamId &new_stream_id)
+{
+  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_bidi);
+  if (error == nullptr) {
+    new_stream_id = this->_next_stream_id_bidi;
+    this->_next_stream_id_bidi += QUIC_STREAM_TYPES;
+  }
+
+  return error;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::delete_stream(QUICStreamId &stream_id)
+{
+  QUICStreamBase *stream = static_cast<QUICStreamBase *>(this->find_stream(stream_id));
+  stream_list.remove(stream);
+  delete stream;
+
+  return nullptr;
+}
+
+QUICStreamBase *
+QUICStreamManagerImpl::_find_stream(QUICStreamId id)
+{
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
+    if (s->id() == id) {
+      return s;
+    }
+  }
+  return nullptr;
+}
+
+QUICStreamBase *
+QUICStreamManagerImpl::_find_or_create_stream(QUICStreamId stream_id)
+{
+  QUICStreamBase *stream = this->_find_stream(stream_id);
+  if (!stream) {
+    if (!this->_local_tp) {
+      return nullptr;
+    }
+
+    ink_assert(this->_local_tp);
+    ink_assert(this->_remote_tp);
+
+    uint64_t local_max_stream_data  = 0;
+    uint64_t remote_max_stream_data = 0;
+    uint64_t nth_stream             = this->_stream_id_to_nth_stream(stream_id);
+
+    switch (QUICTypeUtil::detect_stream_type(stream_id)) {
+    case QUICStreamType::CLIENT_BIDI:
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
+        // client
+        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
+          return nullptr;
+        }
+
+        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
+        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
+      } else {
+        // server
+        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
+          return nullptr;
+        }
+
+        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
+        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
+      }
+
+      break;
+    case QUICStreamType::CLIENT_UNI:
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
+        // client
+        if (this->_remote_max_streams_uni == 0 || nth_stream > this->_remote_max_streams_uni) {
+          return nullptr;
+        }
+      } else {
+        // server
+        if (this->_local_max_streams_uni == 0 || nth_stream > this->_local_max_streams_uni) {
+          return nullptr;
+        }
+      }
+
+      local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
+      remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
+
+      break;
+    case QUICStreamType::SERVER_BIDI:
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
+        // client
+        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
+          return nullptr;
+        }
+
+        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
+        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
+      } else {
+        // server
+        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
+          return nullptr;
+        }
+
+        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
+        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
+      }
+      break;
+    case QUICStreamType::SERVER_UNI:
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
+        if (this->_local_max_streams_uni == 0 || nth_stream > this->_local_max_streams_uni) {
+          return nullptr;
+        }
+      } else {
+        if (this->_remote_max_streams_uni == 0 || nth_stream > this->_remote_max_streams_uni) {
+          return nullptr;
+        }
+      }
+
+      local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
+      remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
+
+      break;
+    default:
+      ink_release_assert(false);
+      break;
+    }
+
+    stream = this->_stream_factory.create(stream_id, local_max_stream_data, remote_max_stream_data);
+    ink_assert(stream != nullptr);
+    stream->set_state_listener(this);
+    this->stream_list.push(stream);
+
+    QUICApplication *application = this->_app_map->get(stream_id);
+    application->on_new_stream(*stream);
+  }
+
+  return stream;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_reordered_bytes() const
+{
+  uint64_t total_bytes = 0;
+
+  // FIXME Iterating all (open + closed) streams is expensive
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
+    total_bytes += s->reordered_bytes();
+  }
+  return total_bytes;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_offset_received() const
+{
+  uint64_t total_offset_received = 0;
+
+  // FIXME Iterating all (open + closed) streams is expensive
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
+    total_offset_received += s->largest_offset_received();
+  }
+  return total_offset_received;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_offset_sent() const
+{
+  return this->_total_offset_sent;
+}
+
+void
+QUICStreamManagerImpl::_add_total_offset_sent(uint32_t sent_byte)
+{
+  // FIXME: use atomic increment
+  this->_total_offset_sent += sent_byte;
+}
+
+uint32_t
+QUICStreamManagerImpl::stream_count() const
+{
+  uint32_t count = 0;
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
+    ++count;
+  }
+  return count;
+}
+
+QUICStream *
+QUICStreamManagerImpl::find_stream(QUICStreamId id)
+{
+  return this->_find_stream(id);
+}
+
+std::vector<QUICFrameType>
+QUICStreamManagerImpl::interests()
+{
+  return {
+    QUICFrameType::STREAM,          QUICFrameType::RESET_STREAM, QUICFrameType::STOP_SENDING,
+    QUICFrameType::MAX_STREAM_DATA, QUICFrameType::MAX_STREAMS,
+  };
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
+{
+  QUICConnectionErrorUPtr error = nullptr;
+
+  switch (frame.type()) {
+  case QUICFrameType::MAX_STREAM_DATA:
+    error = this->_handle_frame(static_cast<const QUICMaxStreamDataFrame &>(frame));
+    break;
+  case QUICFrameType::STREAM_DATA_BLOCKED:
+    // STREAM_DATA_BLOCKED frame is for debugging. Just propagate to streams
+    error = this->_handle_frame(static_cast<const QUICStreamDataBlockedFrame &>(frame));
+    break;
+  case QUICFrameType::STREAM:
+    error = this->_handle_frame(static_cast<const QUICStreamFrame &>(frame));
+    break;
+  case QUICFrameType::STOP_SENDING:
+    error = this->_handle_frame(static_cast<const QUICStopSendingFrame &>(frame));
+    break;
+  case QUICFrameType::RESET_STREAM:
+    error = this->_handle_frame(static_cast<const QUICRstStreamFrame &>(frame));
+    break;
+  case QUICFrameType::MAX_STREAMS:
+    error = this->_handle_frame(static_cast<const QUICMaxStreamsFrame &>(frame));
+    break;
+  default:
+    Debug(tag, "Unexpected frame type: %02x", static_cast<unsigned int>(frame.type()));
+    ink_assert(false);
+    break;
+  }
+
+  return error;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICMaxStreamDataFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICStreamDataBlockedFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICStreamFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICRstStreamFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICStopSendingFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICMaxStreamsFrame &frame)
+{
+  QUICStreamType type = QUICTypeUtil::detect_stream_type(frame.maximum_streams());
+  if (type == QUICStreamType::SERVER_BIDI || type == QUICStreamType::CLIENT_BIDI) {
+    this->_remote_max_streams_bidi = frame.maximum_streams();
+  } else {
+    this->_remote_max_streams_uni = frame.maximum_streams();
+  }
+  return nullptr;
+}
+
+bool
+QUICStreamManagerImpl::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting,
+                                           uint32_t seq_num)
+{
+  if (!this->_is_level_matched(level)) {
+    return false;
+  }
+
+  // workaround fix until support 0-RTT on client
+  if (level == QUICEncryptionLevel::ZERO_RTT) {
+    return false;
+  }
+
+  // Don't send DATA frames if current path is not validated
+  if (!this->_context->path_manager()->get_verified_path().remote_ep().isValid()) {
+    return false;
+  }
+
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
+    if (s->will_generate_frame(level, current_packet_size, ack_eliciting, seq_num)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+QUICFrame *
+QUICStreamManagerImpl::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
+                                      uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num,
+                                      QUICFrameGenerator *owner)
+{
+  QUICFrame *frame = nullptr;
+  if (owner == nullptr) {
+    owner = this;
+  }
+
+  if (!this->_is_level_matched(level)) {
+    return frame;
+  }
+
+  // workaround fix until support 0-RTT on client
+  if (level == QUICEncryptionLevel::ZERO_RTT) {
+    return frame;
+  }
+
+  // FIXME We should pick a stream based on priority
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
+    frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size, seq_num, owner);
+    if (frame) {
+      break;
+    }
+  }
+
+  if (frame != nullptr && frame->type() == QUICFrameType::STREAM) {
+    this->_add_total_offset_sent(static_cast<QUICStreamFrame *>(frame)->data_length());
+  }
+
+  return frame;
+}
+
+void
+QUICStreamManagerImpl::_on_frame_acked(QUICFrameInformationUPtr &info)
+{
+  auto stream = this->_find_stream(info->stream_id);
+  if (stream) {
+    stream->on_frame_acked(info);
+  }
+}
+
+void
+QUICStreamManagerImpl::_on_frame_lost(QUICFrameInformationUPtr &info)
+{
+  auto stream = this->_find_stream(info->stream_id);
+  if (stream) {
+    stream->on_frame_lost(info);
+  }
+}
+
+bool
+QUICStreamManagerImpl::_is_level_matched(QUICEncryptionLevel level)
+{
+  for (auto l : this->_encryption_level_filter) {
+    if (l == level) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void
+QUICStreamManagerImpl::on_stream_state_close(const QUICStream *stream)
+{
+  auto direction = this->_context->connection_info()->direction();
+  switch (QUICTypeUtil::detect_stream_type(stream->id())) {
+  case QUICStreamType::SERVER_BIDI:
+    if (direction == NET_VCONNECTION_OUT) {
+      this->_local_max_streams_bidi += 1;
+    }
+    break;
+  case QUICStreamType::SERVER_UNI:
+    if (direction == NET_VCONNECTION_OUT) {
+      this->_local_max_streams_uni += 1;
+    }
+    break;
+  case QUICStreamType::CLIENT_BIDI:
+    if (direction == NET_VCONNECTION_IN) {
+      this->_local_max_streams_bidi += 1;
+    }
+    break;
+  case QUICStreamType::CLIENT_UNI:
+    if (direction == NET_VCONNECTION_IN) {
+      this->_local_max_streams_uni += 1;
+    }
+    break;
+  }
+}
+
+uint64_t
+QUICStreamManagerImpl::_stream_id_to_nth_stream(QUICStreamId stream_id)
+{
+  return (stream_id / 4) + 1;
+}
diff --git a/iocore/net/quic/QUICStreamManager_native.h b/iocore/net/quic/QUICStreamManager_native.h
new file mode 100644
index 0000000..a63d262
--- /dev/null
+++ b/iocore/net/quic/QUICStreamManager_native.h
@@ -0,0 +1,106 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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.
+ */
+
+#pragma once
+
+#include "QUICStreamManager.h"
+#include "QUICBidirectionalStream.h"
+#include "QUICUnidirectionalStream.h"
+#include "QUICFrameHandler.h"
+#include "QUICFrame.h"
+#include "QUICStreamFactory.h"
+#include "QUICLossDetector.h"
+#include "QUICPathManager.h"
+
+class QUICStreamManagerImpl : public QUICStreamManager, public QUICFrameHandler, public QUICFrameGenerator
+{
+public:
+  QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map);
+  ~QUICStreamManagerImpl();
+
+  virtual void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                        const std::shared_ptr<const QUICTransportParameters> &remote_tp) override;
+  virtual void set_max_streams_bidi(uint64_t max_streams) override;
+  virtual void set_max_streams_uni(uint64_t max_streams) override;
+  virtual uint64_t total_reordered_bytes() const override;
+  virtual uint64_t total_offset_received() const override;
+  virtual uint64_t total_offset_sent() const override;
+
+  virtual uint32_t stream_count() const override;
+  virtual QUICStream *find_stream(QUICStreamId id) override;
+
+  virtual QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id) override;
+  virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr delete_stream(QUICStreamId &stream_id) override;
+  virtual void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error) override;
+
+  // QUICFrameHandler
+  std::vector<QUICFrameType> interests() override;
+  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
+
+  // QUICFrameGenerator
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t timestamp) override;
+  QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
+                            size_t current_packet_size, uint32_t timestamp, QUICFrameGenerator *owner) override;
+  void _on_frame_acked(QUICFrameInformationUPtr &info) override;
+  void _on_frame_lost(QUICFrameInformationUPtr &info) override;
+
+  // QUICStreamStateListener
+  void on_stream_state_close(const QUICStream *stream) override;
+
+  DLL<QUICStreamBase> stream_list;
+
+protected:
+  virtual bool _is_level_matched(QUICEncryptionLevel level) override;
+
+private:
+  QUICConnectionErrorUPtr _handle_frame(const QUICStreamFrame &frame);
+  QUICConnectionErrorUPtr _handle_frame(const QUICRstStreamFrame &frame);
+  QUICConnectionErrorUPtr _handle_frame(const QUICStopSendingFrame &frame);
+  QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamDataFrame &frame);
+  QUICConnectionErrorUPtr _handle_frame(const QUICStreamDataBlockedFrame &frame);
+  QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamsFrame &frame);
+
+  QUICStreamBase *_find_stream(QUICStreamId id);
+  QUICStreamBase *_find_or_create_stream(QUICStreamId stream_id);
+  void _add_total_offset_sent(uint32_t sent_byte);
+  QUICStreamFactory _stream_factory;
+
+  std::shared_ptr<const QUICTransportParameters> _local_tp  = nullptr;
+  std::shared_ptr<const QUICTransportParameters> _remote_tp = nullptr;
+  QUICStreamId _local_max_streams_bidi                      = 0;
+  QUICStreamId _local_max_streams_uni                       = 0;
+  QUICStreamId _remote_max_streams_bidi                     = 0;
+  QUICStreamId _remote_max_streams_uni                      = 0;
+  QUICStreamId _next_stream_id_uni                          = 0;
+  QUICStreamId _next_stream_id_bidi                         = 0;
+  uint64_t _total_offset_sent                               = 0;
+
+  uint64_t _stream_id_to_nth_stream(QUICStreamId stream_id);
+
+  std::array<QUICEncryptionLevel, 2> _encryption_level_filter = {
+    QUICEncryptionLevel::ZERO_RTT,
+    QUICEncryptionLevel::ONE_RTT,
+  };
+};
diff --git a/iocore/net/quic/QUICStreamManager_quiche.cc b/iocore/net/quic/QUICStreamManager_quiche.cc
new file mode 100644
index 0000000..6321cf7
--- /dev/null
+++ b/iocore/net/quic/QUICStreamManager_quiche.cc
@@ -0,0 +1,126 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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 "QUICStreamManager_quiche.h"
+#include "QUICStream_quiche.h"
+
+QUICStreamManagerImpl::QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map)
+  : QUICStreamManager(context, app_map)
+{
+}
+
+QUICStreamManagerImpl::~QUICStreamManagerImpl() {}
+
+void
+QUICStreamManagerImpl::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                                const std::shared_ptr<const QUICTransportParameters> &remote_tp)
+{
+}
+
+void
+QUICStreamManagerImpl::set_max_streams_bidi(uint64_t max_streams)
+{
+}
+
+void
+QUICStreamManagerImpl::set_max_streams_uni(uint64_t max_streams)
+{
+}
+
+uint64_t
+QUICStreamManagerImpl::total_reordered_bytes() const
+{
+  return 0;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_offset_received() const
+{
+  return 0;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_offset_sent() const
+{
+  return 0;
+}
+
+uint32_t
+QUICStreamManagerImpl::stream_count() const
+{
+  return 0;
+}
+
+QUICStream *
+QUICStreamManagerImpl::find_stream(QUICStreamId stream_id)
+{
+  for (QUICStreamImpl *s = this->stream_list.head; s; s = s->link.next) {
+    if (s->id() == stream_id) {
+      return s;
+    }
+  }
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_stream(QUICStreamId stream_id)
+{
+  QUICStreamImpl *stream = new QUICStreamImpl(this->_context->connection_info(), stream_id);
+  this->stream_list.push(stream);
+
+  QUICApplication *application = this->_app_map->get(stream_id);
+  application->on_new_stream(*stream);
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_uni_stream(QUICStreamId &new_stream_id)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_bidi_stream(QUICStreamId &new_stream_id)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::delete_stream(QUICStreamId &stream_id)
+{
+  QUICStreamImpl *stream = static_cast<QUICStreamImpl *>(this->find_stream(stream_id));
+  stream_list.remove(stream);
+  delete stream;
+
+  return nullptr;
+}
+
+void
+QUICStreamManagerImpl::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamManagerImpl::on_stream_state_close(const QUICStream *stream)
+{
+}
diff --git a/iocore/net/quic/QUICStreamManager_quiche.h b/iocore/net/quic/QUICStreamManager_quiche.h
new file mode 100644
index 0000000..8cc409a
--- /dev/null
+++ b/iocore/net/quic/QUICStreamManager_quiche.h
@@ -0,0 +1,57 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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.
+ */
+
+#pragma once
+
+#include "tscore/List.h"
+#include "QUICStreamManager.h"
+#include "QUICStream_quiche.h"
+
+class QUICStreamManagerImpl : public QUICStreamManager
+{
+public:
+  QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map);
+  ~QUICStreamManagerImpl();
+
+  virtual void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                        const std::shared_ptr<const QUICTransportParameters> &remote_tp) override;
+  virtual void set_max_streams_bidi(uint64_t max_streams) override;
+  virtual void set_max_streams_uni(uint64_t max_streams) override;
+  virtual uint64_t total_reordered_bytes() const override;
+  virtual uint64_t total_offset_received() const override;
+  virtual uint64_t total_offset_sent() const override;
+
+  virtual uint32_t stream_count() const override;
+  virtual QUICStream *find_stream(QUICStreamId stream_id) override;
+
+  virtual QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id) override;
+  virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr delete_stream(QUICStreamId &stream_id) override;
+  virtual void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error) override;
+
+  // QUICStreamStateListener
+  void on_stream_state_close(const QUICStream *stream) override;
+
+  DLL<QUICStreamImpl> stream_list;
+};
diff --git a/iocore/net/quic/QUICStreamVCAdapter.cc b/iocore/net/quic/QUICStreamVCAdapter.cc
index 1dc0183..7ed06c4 100644
--- a/iocore/net/quic/QUICStreamVCAdapter.cc
+++ b/iocore/net/quic/QUICStreamVCAdapter.cc
@@ -73,7 +73,7 @@
     if (block->size()) {
       block->consume(reader->start_offset);
       block->_end = std::min(block->start() + len, block->_buf_end);
-      this->_write_vio.ndone += len;
+      this->_write_vio.ndone += block->size();
     }
     reader->consume(block->size());
   }
@@ -234,14 +234,16 @@
   SET_HANDLER(&QUICStreamVCAdapter::state_stream_closed);
 
   this->_read_vio.buffer.clear();
-  this->_read_vio.nbytes = 0;
-  this->_read_vio.op     = VIO::NONE;
-  this->_read_vio.cont   = nullptr;
+  this->_read_vio.nbytes    = 0;
+  this->_read_vio.op        = VIO::NONE;
+  this->_read_vio.cont      = nullptr;
+  this->_read_vio.vc_server = nullptr;
 
   this->_write_vio.buffer.clear();
-  this->_write_vio.nbytes = 0;
-  this->_write_vio.op     = VIO::NONE;
-  this->_write_vio.cont   = nullptr;
+  this->_write_vio.nbytes    = 0;
+  this->_write_vio.op        = VIO::NONE;
+  this->_write_vio.cont      = nullptr;
+  this->_write_vio.vc_server = nullptr;
 }
 
 void
diff --git a/iocore/net/quic/QUICStreamVCAdapter.h b/iocore/net/quic/QUICStreamVCAdapter.h
index 4b18543..aad2c36 100644
--- a/iocore/net/quic/QUICStreamVCAdapter.h
+++ b/iocore/net/quic/QUICStreamVCAdapter.h
@@ -72,6 +72,7 @@
   }
   ~IOInfo()
   {
+    adapter.do_io_close();
     free_MIOBuffer(this->read_buffer);
     free_MIOBuffer(this->write_buffer);
   }
diff --git a/iocore/net/quic/QUICStream_native.h b/iocore/net/quic/QUICStream_native.h
new file mode 100644
index 0000000..354d727
--- /dev/null
+++ b/iocore/net/quic/QUICStream_native.h
@@ -0,0 +1,73 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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.
+ */
+
+#pragma once
+
+#include "QUICStream.h"
+
+class QUICStreamBase : public QUICStream, public QUICFrameGenerator, public QUICFrameRetransmitter
+{
+public:
+  QUICStreamBase() : QUICStream() {}
+  QUICStreamBase(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : QUICStream(cinfo, sid) {}
+  virtual ~QUICStreamBase() {}
+
+  QUICOffset final_offset() const override;
+
+  virtual void stop_sending(QUICStreamErrorUPtr error) override;
+  virtual void reset(QUICStreamErrorUPtr error) override;
+
+  virtual void on_read() override;
+  virtual void on_eos() override;
+
+  void on_frame_acked(QUICFrameInformationUPtr &info);
+  void on_frame_lost(QUICFrameInformationUPtr &info);
+
+  virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICCryptoFrame &frame);
+
+  QUICOffset reordered_bytes() const;
+  virtual QUICOffset largest_offset_received() const;
+  virtual QUICOffset largest_offset_sent() const;
+
+  void set_state_listener(QUICStreamStateListener *listener);
+
+  LINK(QUICStreamBase, link);
+
+protected:
+  QUICOffset _send_offset     = 0;
+  QUICOffset _reordered_bytes = 0;
+
+  QUICStreamStateListener *_state_listener = nullptr;
+
+  void _notify_state_change();
+
+  void _records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame);
+  void _records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame);
+  void _records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame);
+  void _records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame);
+};
diff --git a/iocore/net/quic/QUICStream_quiche.cc b/iocore/net/quic/QUICStream_quiche.cc
new file mode 100644
index 0000000..33ddd02
--- /dev/null
+++ b/iocore/net/quic/QUICStream_quiche.cc
@@ -0,0 +1,94 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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 "QUICStream_quiche.h"
+#include "QUICStreamAdapter.h"
+
+QUICStreamImpl::QUICStreamImpl() {}
+
+QUICStreamImpl::QUICStreamImpl(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : QUICStream(cinfo, sid) {}
+
+QUICOffset
+QUICStreamImpl::final_offset() const
+{
+  return 0;
+}
+
+void
+QUICStreamImpl::stop_sending(QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamImpl::reset(QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamImpl::on_read()
+{
+}
+
+void
+QUICStreamImpl::on_eos()
+{
+}
+
+void
+QUICStreamImpl::receive_data(quiche_conn *quiche_con)
+{
+  uint8_t buf[4096];
+  bool fin;
+  ssize_t read_len = 0;
+
+  while ((read_len = quiche_conn_stream_recv(quiche_con, this->_id, buf, sizeof(buf), &fin)) > 0) {
+    this->_adapter->write(this->_received_bytes, buf, read_len, fin);
+    this->_received_bytes += read_len;
+  }
+
+  this->_adapter->encourge_read();
+}
+
+void
+QUICStreamImpl::send_data(quiche_conn *quiche_con)
+{
+  bool fin    = false;
+  ssize_t len = 0;
+
+  len = quiche_conn_stream_capacity(quiche_con, this->_id);
+  if (len <= 0) {
+    return;
+  }
+  Ptr<IOBufferBlock> block = this->_adapter->read(len);
+  if (this->_adapter->total_len() == this->_sent_bytes + block->size()) {
+    fin = true;
+  }
+  if (block->size() > 0 || fin) {
+    ssize_t written_len =
+      quiche_conn_stream_send(quiche_con, this->_id, reinterpret_cast<uint8_t *>(block->start()), block->size(), fin);
+    if (written_len >= 0) {
+      this->_sent_bytes += written_len;
+    }
+  }
+  this->_adapter->encourge_write();
+}
diff --git a/iocore/net/quic/QUICStream_quiche.h b/iocore/net/quic/QUICStream_quiche.h
new file mode 100644
index 0000000..c24282b
--- /dev/null
+++ b/iocore/net/quic/QUICStream_quiche.h
@@ -0,0 +1,51 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @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.
+ */
+
+#pragma once
+
+#include "QUICStream.h"
+#include <quiche.h>
+
+class QUICStreamImpl : public QUICStream
+{
+public:
+  QUICStreamImpl();
+  QUICStreamImpl(QUICConnectionInfoProvider *cinfo, QUICStreamId sid);
+  void receive_data(quiche_conn *quiche_con);
+  void send_data(quiche_conn *quiche_con);
+
+  // QUICStream
+  virtual QUICOffset final_offset() const override;
+
+  virtual void stop_sending(QUICStreamErrorUPtr error) override;
+  virtual void reset(QUICStreamErrorUPtr error) override;
+
+  virtual void on_read() override;
+  virtual void on_eos() override;
+
+  LINK(QUICStreamImpl, link);
+
+private:
+  uint64_t _received_bytes = 0;
+  uint64_t _sent_bytes     = 0;
+};
diff --git a/iocore/net/quic/QUICTokenCreator.cc b/iocore/net/quic/QUICTokenCreator.cc
index 9d1ef62..5faf03f 100644
--- a/iocore/net/quic/QUICTokenCreator.cc
+++ b/iocore/net/quic/QUICTokenCreator.cc
@@ -34,7 +34,7 @@
 
 QUICFrame *
 QUICTokenCreator::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                 size_t current_packet_size, uint32_t seq_num)
+                                 size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner)
 {
   QUICFrame *frame = nullptr;
 
diff --git a/iocore/net/quic/QUICTokenCreator.h b/iocore/net/quic/QUICTokenCreator.h
index e107fb9..5675937 100644
--- a/iocore/net/quic/QUICTokenCreator.h
+++ b/iocore/net/quic/QUICTokenCreator.h
@@ -32,7 +32,7 @@
 
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
 private:
   void _on_frame_lost(QUICFrameInformationUPtr &info) override;
diff --git a/iocore/net/quic/QUICTransportParameters.cc b/iocore/net/quic/QUICTransportParameters.cc
index 72d7f6d..eff5e7d 100644
--- a/iocore/net/quic/QUICTransportParameters.cc
+++ b/iocore/net/quic/QUICTransportParameters.cc
@@ -381,26 +381,24 @@
   decltype(this->_parameters)::const_iterator ite;
 
   // MUSTs
-  if (version == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
-      // We cannot check the length because it's not a fixed length.
-    } else {
-      return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID);
-    }
+  if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+    // We cannot check the length because it's not a fixed length.
+  } else {
+    return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID);
+  }
 
-    if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID)) != this->_parameters.end()) {
-      // We cannot check the length because it's not a fixed length.
-    } else {
-      return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID);
-    }
+  if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID)) != this->_parameters.end()) {
+    // We cannot check the length because it's not a fixed length.
+  } else {
+    return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID);
+  }
 
-    // MUSTs if the server sent a Retry packet, but MUST NOT if the server did not send a Retry packet
-    // TODO Check if the server sent Retry packet
-    if ((ite = this->_parameters.find(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
-      // return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
-    } else {
-      // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
-    }
+  // MUSTs if the server sent a Retry packet, but MUST NOT if the server did not send a Retry packet
+  // TODO Check if the server sent Retry packet
+  if ((ite = this->_parameters.find(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+    // return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
+  } else {
+    // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
   }
 
   // MAYs
diff --git a/iocore/net/quic/QUICTypes.h b/iocore/net/quic/QUICTypes.h
index 78af5ea..76f1f16 100644
--- a/iocore/net/quic/QUICTypes.h
+++ b/iocore/net/quic/QUICTypes.h
@@ -51,8 +51,8 @@
 // Note: Fix "Supported Version" field in test case of QUICPacketFactory_Create_VersionNegotiationPacket
 // Note: Fix QUIC_ALPN_PROTO_LIST in QUICConfig.cc
 constexpr QUICVersion QUIC_SUPPORTED_VERSIONS[] = {
+  0x00000001,
   0xff00001d,
-  0xff00001b,
 };
 constexpr QUICVersion QUIC_EXERCISE_VERSION1 = 0x1a2a3a4a;
 constexpr QUICVersion QUIC_EXERCISE_VERSION2 = 0x5a6a7a8a;
diff --git a/iocore/net/quic/QUICUnidirectionalStream.cc b/iocore/net/quic/QUICUnidirectionalStream.cc
index 599af3a..2352227 100644
--- a/iocore/net/quic/QUICUnidirectionalStream.cc
+++ b/iocore/net/quic/QUICUnidirectionalStream.cc
@@ -28,7 +28,7 @@
 // QUICSendStream
 //
 QUICSendStream::QUICSendStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid, uint64_t send_max_stream_data)
-  : QUICStream(cinfo, sid), _remote_flow_controller(send_max_stream_data, _id), _state(nullptr, &this->_progress_sa)
+  : QUICStreamBase(cinfo, sid), _remote_flow_controller(send_max_stream_data, _id), _state(nullptr, &this->_progress_sa)
 {
   QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(),
                     this->_remote_flow_controller.current_limit());
@@ -48,9 +48,13 @@
 
 QUICFrame *
 QUICSendStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                               size_t current_packet_size, uint32_t seq_num)
+                               size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner)
 {
-  QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), this);
+  if (owner == nullptr) {
+    owner = this;
+  }
+
+  QUICFrame *frame = this->create_retransmitted_frame(buf, level, maximum_frame_size, this->_issue_frame_id(), owner);
   if (frame != nullptr) {
     ink_assert(frame->type() == QUICFrameType::STREAM);
     this->_records_stream_frame(level, *static_cast<QUICStreamFrame *>(frame));
@@ -59,7 +63,7 @@
 
   // RESET_STREAM
   if (this->_reset_reason && !this->_is_reset_sent) {
-    frame = QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason, this->_issue_frame_id(), this);
+    frame = QUICFrameFactory::create_rst_stream_frame(buf, *this->_reset_reason, this->_issue_frame_id(), owner);
     if (frame->size() > maximum_frame_size) {
       frame->~QUICFrame();
       return nullptr;
@@ -101,8 +105,8 @@
     uint64_t stream_credit = this->_remote_flow_controller.credit();
     if (stream_credit == 0) {
       // STREAM_DATA_BLOCKED
-      frame =
-        this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
+      frame = this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num,
+                                                           owner);
       return frame;
     }
 
@@ -127,7 +131,7 @@
   // STREAM - Pure FIN or data length is lager than 0
   // FIXME has_length_flag and has_offset_flag should be configurable
   frame = QUICFrameFactory::create_stream_frame(buf, block, this->_id, this->_send_offset, fin, true, true, this->_issue_frame_id(),
-                                                this);
+                                                owner);
   if (!this->_state.is_allowed_to_send(*frame)) {
     QUICStreamDebug("Canceled sending %s frame due to the stream state", QUICDebugNames::frame_type(frame->type()));
     return frame;
@@ -241,7 +245,7 @@
 //
 QUICReceiveStream::QUICReceiveStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
                                      uint64_t recv_max_stream_data)
-  : QUICStream(cinfo, sid),
+  : QUICStreamBase(cinfo, sid),
     _local_flow_controller(rtt_provider, recv_max_stream_data, _id),
     _flow_control_buffer_size(recv_max_stream_data),
     _state(this, nullptr)
@@ -288,13 +292,17 @@
 
 QUICFrame *
 QUICReceiveStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                  size_t current_packet_size, uint32_t seq_num)
+                                  size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner)
 {
   QUICFrame *frame = nullptr;
+  if (owner == nullptr) {
+    owner = this;
+  }
+
   // STOP_SENDING
   if (this->_stop_sending_reason && !this->_is_stop_sending_sent) {
-    frame =
-      QUICFrameFactory::create_stop_sending_frame(buf, this->id(), this->_stop_sending_reason->code, this->_issue_frame_id(), this);
+    frame = QUICFrameFactory::create_stop_sending_frame(buf, this->id(), this->_stop_sending_reason->code, this->_issue_frame_id(),
+                                                        owner);
     if (frame->size() > maximum_frame_size) {
       frame->~QUICFrame();
       return nullptr;
@@ -308,7 +316,8 @@
   }
 
   // MAX_STREAM_DATA
-  frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
+  frame =
+    this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num, owner);
   // maximum_frame_size should be checked in QUICFlowController
   return frame;
 }
diff --git a/iocore/net/quic/QUICUnidirectionalStream.h b/iocore/net/quic/QUICUnidirectionalStream.h
index 315c24f..e94cc14 100644
--- a/iocore/net/quic/QUICUnidirectionalStream.h
+++ b/iocore/net/quic/QUICUnidirectionalStream.h
@@ -24,8 +24,9 @@
 #pragma once
 
 #include "QUICStream.h"
+#include "QUICStream_native.h"
 
-class QUICSendStream : public QUICStream
+class QUICSendStream : public QUICStreamBase
 {
 public:
   QUICSendStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid, uint64_t send_max_stream_data);
@@ -39,7 +40,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
   virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame) override;
   virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame) override;
@@ -66,7 +67,7 @@
   void _on_frame_lost(QUICFrameInformationUPtr &info) override;
 };
 
-class QUICReceiveStream : public QUICStream, public QUICTransferProgressProvider
+class QUICReceiveStream : public QUICStreamBase, public QUICTransferProgressProvider
 {
 public:
   QUICReceiveStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
@@ -81,7 +82,7 @@
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t seq_num) override;
+                            size_t current_packet_size, uint32_t seq_num, QUICFrameGenerator *owner) override;
 
   virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame) override;
   virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame) override;
diff --git a/iocore/net/quic/test/test_QUICAckFrameCreator.cc b/iocore/net/quic/test/test_QUICAckFrameCreator.cc
index cd65277..2090dad 100644
--- a/iocore/net/quic/test/test_QUICAckFrameCreator.cc
+++ b/iocore/net/quic/test/test_QUICAckFrameCreator.cc
@@ -32,13 +32,13 @@
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
   // Initial state
-  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame == nullptr);
 
   // One packet
   ack_manager.update(level, 1, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
@@ -54,7 +54,7 @@
   ack_manager.update(level, 5, 1, false);
   ack_manager.update(level, 3, 1, false);
   ack_manager.update(level, 4, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
@@ -66,7 +66,7 @@
   ack_manager.update(level, 6, 1, false);
   ack_manager.update(level, 7, 1, false);
   ack_manager.update(level, 10, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 1);
@@ -79,13 +79,13 @@
   ack_manager.on_frame_acked(frame->id());
 
   CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   CHECK(ack_frame == nullptr);
 
   ack_manager.update(level, 11, 1, false);
   ack_manager.update(level, 12, 1, false);
   ack_manager.update(level, 13, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
@@ -101,10 +101,10 @@
   ack_manager.update(level, 15, 1, true);
   ack_manager.update(level, 16, 1, true);
   CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
 
   ack_manager.update(level, 17, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
@@ -152,7 +152,7 @@
     ack_manager.update(level, 1, 1, false);
     CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
-    QUICFrame *frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    QUICFrame *frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     CHECK(frame != nullptr);
     frame->~QUICFrame();
 
@@ -162,7 +162,7 @@
     ack_manager.update(level, 3, 1, false);
     CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
-    frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     CHECK(frame != nullptr);
     frame->~QUICFrame();
   }
@@ -219,7 +219,7 @@
     QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
 
     uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
-    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame == nullptr);
 
@@ -233,7 +233,7 @@
     sleep(1);
     Thread::get_hrtime_updated();
     CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
-    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     frame     = static_cast<QUICAckFrame *>(ack_frame);
 
     CHECK(frame->ack_block_count() == 0);
@@ -251,7 +251,7 @@
 
   // Initial state
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
-  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame == nullptr);
 
@@ -261,7 +261,7 @@
   ack_manager.update(level, 8, 1, false);
   ack_manager.update(level, 9, 1, false);
 
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 2);
@@ -274,7 +274,7 @@
 
   ack_manager.update(level, 7, 1, false);
   ack_manager.update(level, 4, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 1);
@@ -331,7 +331,7 @@
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
   // Initial state
-  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame == nullptr);
 
@@ -341,7 +341,7 @@
   ack_manager.update(level, 8, 1, false);
   ack_manager.update(level, 9, 1, false);
 
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 2);
@@ -352,7 +352,7 @@
 
   ack_manager.on_frame_lost(frame->id());
   CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 2);
@@ -367,7 +367,7 @@
   ack_manager.update(level, 7, 1, false);
   ack_manager.update(level, 4, 1, false);
 
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 1);
@@ -388,7 +388,7 @@
     uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
     // Initial state
-    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame == nullptr);
 
@@ -400,7 +400,7 @@
 
     CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
-    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     frame     = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame != nullptr);
     CHECK(frame->ack_block_count() == 0);
@@ -421,7 +421,7 @@
     uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
     // Initial state
-    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame == nullptr);
 
@@ -433,7 +433,7 @@
 
     CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
-    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
+    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0, nullptr);
     frame     = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame != nullptr);
     CHECK(frame->ack_block_count() == 0);
diff --git a/iocore/net/quic/test/test_QUICFlowController.cc b/iocore/net/quic/test/test_QUICFlowController.cc
index 598b74e..a4958f2 100644
--- a/iocore/net/quic/test/test_QUICFlowController.cc
+++ b/iocore/net/quic/test/test_QUICFlowController.cc
@@ -116,7 +116,7 @@
   fc.forward_limit(2048);
   CHECK(fc.current_offset() == 1024);
   CHECK(fc.current_limit() == 2048);
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0, nullptr);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::MAX_DATA);
 
@@ -168,7 +168,7 @@
   CHECK(fc.current_offset() == 1000);
   CHECK(fc.current_limit() == 1024);
   CHECK(ret != 0);
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0, nullptr);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::DATA_BLOCKED);
 
@@ -201,7 +201,7 @@
 
   CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, true, 0));
   // if there're anything to send
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0, nullptr);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::DATA_BLOCKED);
 
@@ -234,9 +234,9 @@
 
   CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, true, 0));
   // if there're anything to send
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 0, 0, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 0, 0, 0, nullptr);
   CHECK(frame == nullptr);
-  frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
+  frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0, nullptr);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::DATA_BLOCKED);
 
@@ -300,7 +300,7 @@
   fc.forward_limit(2048);
   CHECK(fc.current_offset() == 1024);
   CHECK(fc.current_limit() == 2048);
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0, nullptr);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::MAX_STREAM_DATA);
 
@@ -377,36 +377,36 @@
     QUICRemoteConnectionFlowController fc(1024);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     CHECK(!frame);
 
     ret = fc.update(1024);
     CHECK(ret == 0);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICDataBlockedFrame *>(frame)->offset() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(!frame);
 
     // Retransmit
-    fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(id);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICDataBlockedFrame *>(frame)->offset() == 1024);
 
     // Don't send if it was not blocked
-    fc.on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(frame->id());
     fc.forward_limit(2048);
     ret   = fc.update(1536);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     CHECK(!frame);
 
     // This should not be retransmission
     ret   = fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICDataBlockedFrame *>(frame)->offset() == 2048);
   }
@@ -418,36 +418,36 @@
     QUICRemoteStreamFlowController fc(1024, 0);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     CHECK(!frame);
 
     ret = fc.update(1024);
     CHECK(ret == 0);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICStreamDataBlockedFrame *>(frame)->offset() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(!frame);
 
     // Retransmit
-    fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(id);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICStreamDataBlockedFrame *>(frame)->offset() == 1024);
 
     // Don't send if it was not blocked
-    fc.on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(frame->id());
     fc.forward_limit(2048);
     ret   = fc.update(1536);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     CHECK(!frame);
 
     // This should not be retransmission
     ret   = fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICStreamDataBlockedFrame *>(frame)->offset() == 2048);
   }
@@ -459,31 +459,31 @@
     QUICLocalConnectionFlowController fc(&rp, 1024);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     CHECK(!frame);
 
     fc.update(1024);
     fc.forward_limit(1024);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxDataFrame *>(frame)->maximum_data() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(!frame);
 
     // Retransmit
-    fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(id);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxDataFrame *>(frame)->maximum_data() == 1024);
 
     // Send new one if it was updated
-    fc.on_frame_lost(id);
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(id);
     fc.forward_limit(2048);
     fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxDataFrame *>(frame)->maximum_data() == 2048);
   }
@@ -495,31 +495,31 @@
     QUICLocalStreamFlowController fc(&rp, 1024, 0);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     CHECK(!frame);
 
     fc.update(1024);
     fc.forward_limit(1024);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxStreamDataFrame *>(frame)->maximum_stream_data() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(!frame);
 
     // Retransmit
-    fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(id);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxStreamDataFrame *>(frame)->maximum_stream_data() == 1024);
 
     // Send new one if it was updated
-    fc.on_frame_lost(id);
+    static_cast<QUICFrameGenerator *>(&fc)->on_frame_lost(id);
     fc.forward_limit(2048);
     fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxStreamDataFrame *>(frame)->maximum_stream_data() == 2048);
   }
diff --git a/iocore/net/quic/test/test_QUICKeyGenerator.cc b/iocore/net/quic/test/test_QUICKeyGenerator.cc
index cd9ff38..aa61a30 100644
--- a/iocore/net/quic/test/test_QUICKeyGenerator.cc
+++ b/iocore/net/quic/test/test_QUICKeyGenerator.cc
@@ -42,16 +42,17 @@
   {
     QUICKeyGenerator keygen(QUICKeyGenerator::Context::CLIENT);
 
-    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8};
+    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x08"), 8};
 
-    uint8_t expected_client_key[] = {0xfc, 0x4a, 0x14, 0x7a, 0x7e, 0xe9, 0x70, 0x29, 0x1b, 0x8f, 0x1c, 0x3, 0x2d, 0x2c, 0x40, 0xf9};
-    uint8_t expected_client_iv[]  = {0x1e, 0x6a, 0x5d, 0xdb, 0x7c, 0x1d, 0x1a, 0xa7, 0xa0, 0xfd, 0x70, 0x5};
-    uint8_t expected_client_hp[] = {0x43, 0x1d, 0x22, 0x82, 0xb4, 0x7b, 0xb9, 0x3f, 0xeb, 0xd2, 0xcf, 0x19, 0x85, 0x21, 0xe2, 0xbe};
+    uint8_t expected_client_key[] = {0x1f, 0x36, 0x96, 0x13, 0xdd, 0x76, 0xd5, 0x46,
+                                     0x77, 0x30, 0xef, 0xcb, 0xe3, 0xb1, 0xa2, 0x2d};
+    uint8_t expected_client_iv[]  = {0xfa, 0x04, 0x4b, 0x2f, 0x42, 0xa3, 0xfd, 0x3b, 0x46, 0xfb, 0x25, 0x5c};
+    uint8_t expected_client_hp[] = {0x9f, 0x50, 0x44, 0x9e, 0x04, 0xa0, 0xe8, 0x10, 0x28, 0x3a, 0x1e, 0x99, 0x33, 0xad, 0xed, 0xd2};
 
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
     pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
-    keygen.generate(0xff00001b, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
+    keygen.generate(0x00000001, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_key(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
 
@@ -67,17 +68,17 @@
   {
     QUICKeyGenerator keygen(QUICKeyGenerator::Context::SERVER);
 
-    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8};
+    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x08"), 8};
 
-    uint8_t expected_server_key[] = {0x60, 0xc0, 0x2f, 0xa6, 0x12, 0x1e, 0xb1, 0xab,
-                                     0xa4, 0x35, 0x1f, 0x2a, 0x63, 0xb0, 0xac, 0xf8};
-    uint8_t expected_server_iv[]  = {0x38, 0xd, 0xf3, 0xc0, 0xf2, 0x8d, 0x94, 0x7, 0x76, 0x5c, 0x55, 0xa1};
-    uint8_t expected_server_hp[] = {0x92, 0xe8, 0x67, 0xb1, 0x20, 0xb1, 0x3f, 0x40, 0x9c, 0x1a, 0xa8, 0xef, 0x54, 0x30, 0x53, 0x51};
+    uint8_t expected_server_key[] = {0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c,
+                                     0x88, 0xf0, 0xf3, 0x79, 0xb6, 0x06, 0x7e, 0x37};
+    uint8_t expected_server_iv[]  = {0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3e};
+    uint8_t expected_server_hp[] = {0xc2, 0x06, 0xb8, 0xd9, 0xb9, 0xf0, 0xf3, 0x76, 0x44, 0x43, 0x0b, 0x49, 0x0e, 0xea, 0xa3, 0x14};
 
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
     pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
-    keygen.generate(0xff00001b, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
+    keygen.generate(0x00000001, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_key(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
 
diff --git a/iocore/net/quic/test/test_QUICLossDetector.cc b/iocore/net/quic/test/test_QUICLossDetector.cc
index 5dd8271..96e663a 100644
--- a/iocore/net/quic/test/test_QUICLossDetector.cc
+++ b/iocore/net/quic/test/test_QUICLossDetector.cc
@@ -59,7 +59,7 @@
     // Check initial state
     uint8_t frame_buffer[1024] = {0};
     CHECK(g.lost_frame_count == 0);
-    QUICFrame *ping_frame = g.generate_frame(frame_buffer, QUICEncryptionLevel::HANDSHAKE, 4, UINT16_MAX, 0, 0);
+    QUICFrame *ping_frame = g.generate_frame(frame_buffer, QUICEncryptionLevel::HANDSHAKE, 4, UINT16_MAX, 0, 0, nullptr);
 
     uint8_t raw[4];
     size_t len             = 0;
@@ -274,7 +274,7 @@
     afm.update(level, pn9, payload_len, false);
     afm.update(level, pn10, payload_len, false);
     uint8_t buf[QUICFrame::MAX_INSTANCE_SIZE];
-    QUICFrame *x = afm.generate_frame(buf, level, 2048, 2048, 0, 0);
+    QUICFrame *x = afm.generate_frame(buf, level, 2048, 2048, 0, 0, nullptr);
     frame        = static_cast<QUICAckFrame *>(x);
     ink_hrtime_sleep(HRTIME_MSECONDS(1000));
     detector.handle_frame(level, *frame);
diff --git a/iocore/net/quic/test/test_QUICPacketFactory.cc b/iocore/net/quic/test/test_QUICPacketFactory.cc
index 57bf6b3..348ec34 100644
--- a/iocore/net/quic/test/test_QUICPacketFactory.cc
+++ b/iocore/net/quic/test/test_QUICPacketFactory.cc
@@ -56,8 +56,8 @@
     0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Destination Connection ID
     0x08,                                           // SCID Len
     0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Source Connection ID
+    0x00, 0x00, 0x00, 0x01,                         // Supported Version
     0xff, 0x00, 0x00, 0x1d,                         // Supported Version
-    0xff, 0x00, 0x00, 0x1b,                         // Supported Version
     0x5a, 0x6a, 0x7a, 0x8a,                         // Exercise Version
   };
   uint8_t buf[1024] = {0};
diff --git a/iocore/net/quic/test/test_QUICPathValidator.cc b/iocore/net/quic/test/test_QUICPathValidator.cc
index cd96ef7..57fe773 100644
--- a/iocore/net/quic/test/test_QUICPathValidator.cc
+++ b/iocore/net/quic/test/test_QUICPathValidator.cc
@@ -63,7 +63,7 @@
     CHECK(pv_c.is_validating(path));
     CHECK(!pv_c.is_validated(path));
     REQUIRE(pv_c.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, seq_num));
-    auto frame = pv_c.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, seq_num);
+    auto frame = pv_c.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, seq_num, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::PATH_CHALLENGE);
     CHECK(pv_c.is_validating(path));
@@ -80,7 +80,7 @@
     CHECK(!pv_s.is_validated(path));
     REQUIRE(pv_s.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, seq_num));
     frame->~QUICFrame();
-    frame = pv_s.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, seq_num);
+    frame = pv_s.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, seq_num, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::PATH_RESPONSE);
     CHECK(!pv_s.is_validating(path));
diff --git a/iocore/net/quic/test/test_QUICStream.cc b/iocore/net/quic/test/test_QUICStream.cc
index 9261781..8f9019d 100644
--- a/iocore/net/quic/test/test_QUICStream.cc
+++ b/iocore/net/quic/test/test_QUICStream.cc
@@ -241,28 +241,28 @@
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
@@ -270,7 +270,7 @@
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
@@ -281,7 +281,7 @@
     // This should send a frame
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
@@ -292,13 +292,13 @@
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
 
     // Update window
@@ -306,7 +306,7 @@
 
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
   }
@@ -342,21 +342,21 @@
     write_buffer->write(data1, sizeof(data1));
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     // Generate STREAM frame
-    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     frame1 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr) == nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     // Write data2
     write_buffer->write(data2, sizeof(data2));
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     // Lost the frame
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     // Regenerate a frame
-    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0, 0, nullptr);
     // Lost data should be resent first
     frame2 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
@@ -385,15 +385,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->reset(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
     // Don't send it again until it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr) == nullptr);
     // Loss the frame
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
   }
@@ -418,15 +418,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
     // Don't send it again until it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr) == nullptr);
     // Loss the frame
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
   }
@@ -449,7 +449,7 @@
     adapter1.do_io_write(&mock_cont1, INT64_MAX, write_buffer_reader);
     SCOPED_MUTEX_LOCK(lock1, adapter1.mutex, this_ethread());
     stream1->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream1.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream1->generate_frame(frame_buf, level, 4096, 0, 0, 0);
+    frame = stream1->generate_frame(frame_buf, level, 4096, 0, 0, 0, nullptr);
     CHECK(frame == nullptr);
 
     // RESET_STREAM
@@ -461,7 +461,7 @@
     adapter2.do_io_write(&mock_cont2, INT64_MAX, write_buffer_reader);
     SCOPED_MUTEX_LOCK(lock2, adapter2.mutex, this_ethread());
     stream2->reset(QUICStreamErrorUPtr(new QUICStreamError(stream2.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream2->generate_frame(frame_buf, level, 4096, 0, 0, 0);
+    frame = stream2->generate_frame(frame_buf, level, 4096, 0, 0, 0, nullptr);
     CHECK(frame == nullptr);
 
     // STREAM
@@ -475,7 +475,7 @@
     const char data[] = "this is a test data";
     write_buffer->write(data, sizeof(data));
     adapter3.handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    frame = stream3->generate_frame(frame_buf, level, 4096, 0, 0, 0);
+    frame = stream3->generate_frame(frame_buf, level, 4096, 0, 0, 0, nullptr);
     CHECK(frame == nullptr);
   }
 }
@@ -674,15 +674,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
     // Don't send it again until it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr) == nullptr);
     // Loss the frame
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
   }
@@ -701,7 +701,7 @@
     MockContinuation mock_cont1(adapter1.mutex);
     SCOPED_MUTEX_LOCK(lock1, adapter1.mutex, this_ethread());
     stream1->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream1.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream1->generate_frame(frame_buf, level, 4096, 0, 0, 0);
+    frame = stream1->generate_frame(frame_buf, level, 4096, 0, 0, 0, nullptr);
     CHECK(frame == nullptr);
   }
 }
@@ -782,28 +782,28 @@
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
@@ -811,7 +811,7 @@
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
@@ -822,7 +822,7 @@
     // This should send a frame
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
@@ -833,13 +833,13 @@
     write_buffer->write(data, 1024);
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
 
     // Update window
@@ -847,7 +847,7 @@
 
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     CHECK(frame->type() == QUICFrameType::STREAM);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
   }
@@ -881,21 +881,21 @@
     write_buffer->write(data1, sizeof(data1));
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     // Generate STREAM frame
-    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     frame1 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr) == nullptr);
     CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     // Write data2
     write_buffer->write(data2, sizeof(data2));
     adapter.handleEvent(VC_EVENT_WRITE_READY, nullptr);
     // Lost the frame
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     // Regenerate a frame
-    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0, 0, nullptr);
     // Lost data should be resent first
     frame2 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
@@ -922,15 +922,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->reset(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
     // Don't send it again until it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr) == nullptr);
     // Loss the frame
-    stream->on_frame_lost(frame->id());
+    static_cast<QUICFrameGenerator *>(stream.get())->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0, nullptr);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
   }
@@ -951,7 +951,7 @@
     adapter2.do_io_write(&mock_cont2, INT64_MAX, write_buffer_reader);
     SCOPED_MUTEX_LOCK(lock2, adapter2.mutex, this_ethread());
     stream2->reset(QUICStreamErrorUPtr(new QUICStreamError(stream2.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream2->generate_frame(frame_buf, level, 4096, 0, 0, 0);
+    frame = stream2->generate_frame(frame_buf, level, 4096, 0, 0, 0, nullptr);
     CHECK(frame == nullptr);
 
     // STREAM
@@ -964,7 +964,7 @@
     const char data[] = "this is a test data";
     write_buffer->write(data, sizeof(data));
     adapter3.handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    frame = stream3->generate_frame(frame_buf, level, 4096, 0, 0, 0);
+    frame = stream3->generate_frame(frame_buf, level, 4096, 0, 0, 0, nullptr);
     CHECK(frame == nullptr);
   }
 }
@@ -980,14 +980,14 @@
     std::unique_ptr<QUICBidirectionalStream> stream_bidi(
       new QUICBidirectionalStream(&rtt_provider, &cinfo_provider, 0, 1024, 1024));
     CHECK(stream_bidi->will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, 0) == false);
-    CHECK(stream_bidi->generate_frame(buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, 0) == nullptr);
+    CHECK(stream_bidi->generate_frame(buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, 0, nullptr) == nullptr);
 
     std::unique_ptr<QUICSendStream> stream_uni1(new QUICSendStream(&cinfo_provider, 2, 1024));
     CHECK(stream_uni1->will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, 0) == false);
-    CHECK(stream_uni1->generate_frame(buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, 0) == nullptr);
+    CHECK(stream_uni1->generate_frame(buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, 0, nullptr) == nullptr);
 
     std::unique_ptr<QUICReceiveStream> stream_uni2(new QUICReceiveStream(&rtt_provider, &cinfo_provider, 3, 1024));
     CHECK(stream_uni2->will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, 0) == false);
-    CHECK(stream_uni2->generate_frame(buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, 0) == nullptr);
+    CHECK(stream_uni2->generate_frame(buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, 0, nullptr) == nullptr);
   }
 }
diff --git a/iocore/net/quic/test/test_QUICStreamManager.cc b/iocore/net/quic/test/test_QUICStreamManager.cc
index ad9443d..130454f 100644
--- a/iocore/net/quic/test/test_QUICStreamManager.cc
+++ b/iocore/net/quic/test/test_QUICStreamManager.cc
@@ -26,6 +26,7 @@
 #include <memory>
 
 #include "quic/QUICStreamManager.h"
+#include "quic/QUICStreamManager_native.h"
 #include "quic/QUICFrame.h"
 #include "quic/Mock.h"
 
@@ -39,7 +40,7 @@
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
   MockQUICConnectionInfoProvider cinfo_provider;
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08,      // parameter id - initial_max_streams_bidi
@@ -103,7 +104,7 @@
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
   MockQUICConnectionInfoProvider cinfo_provider;
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
   std::shared_ptr<QUICTransportParameters> local_tp  = std::make_shared<QUICTransportParametersInEncryptedExtensions>();
   std::shared_ptr<QUICTransportParameters> remote_tp = std::make_shared<QUICTransportParametersInClientHello>();
   sm.init_flow_control_params(local_tp, remote_tp);
@@ -128,7 +129,7 @@
   MockQUICConnection connection;
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08,                  // parameter id - initial_max_streams_bidi
@@ -183,7 +184,7 @@
   MockQUICConnection connection;
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08,                  // parameter id - initial_max_streams_bidi
@@ -232,12 +233,12 @@
   // total_offset should be a integer in unit of octets
   uint8_t frame_buf[4096];
   mock_app.send(reinterpret_cast<uint8_t *>(block_1024->buf()), 1024, 0);
-  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0, 0);
+  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0, 0, nullptr);
   CHECK(sm.total_offset_sent() == 1024);
 
   // total_offset should be a integer in unit of octets
   mock_app.send(reinterpret_cast<uint8_t *>(block_1024->buf()), 1024, 4);
-  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0, 0);
+  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0, 0, nullptr);
   CHECK(sm.total_offset_sent() == 2048);
 
   // Wait for event processing
@@ -251,7 +252,7 @@
   MockQUICConnection connection;
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08, // parameter id - initial_max_streams_bidi
diff --git a/iocore/net/test_certlookup.cc b/iocore/net/test_certlookup.cc
index fa1d793..1c0e743 100644
--- a/iocore/net/test_certlookup.cc
+++ b/iocore/net/test_certlookup.cc
@@ -43,11 +43,11 @@
 
   box = REGRESSION_TEST_PASSED;
 
-  assert(wild != nullptr);
-  assert(notwild != nullptr);
-  assert(b_notwild != nullptr);
-  assert(foo != nullptr);
-  assert(all_com != nullptr);
+  ink_assert(wild != nullptr);
+  ink_assert(notwild != nullptr);
+  ink_assert(b_notwild != nullptr);
+  ink_assert(foo != nullptr);
+  ink_assert(all_com != nullptr);
 
   box.check(lookup.insert("www.foo.com", foo_cc) >= 0, "insert host context");
   // Insert the same SSL_CTX instance under another name too
diff --git a/iocore/utils/Makefile.am b/iocore/utils/Makefile.am
index fa2d39f..13115e3 100644
--- a/iocore/utils/Makefile.am
+++ b/iocore/utils/Makefile.am
@@ -20,7 +20,7 @@
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
 	-I$(abs_top_srcdir)/iocore/eventsystem \
-	$(TS_INCLUDES)
+	@SWOC_INCLUDES@ $(TS_INCLUDES)
 
 noinst_LIBRARIES = libinkutils.a
 
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 592ffa9..e966b9f 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -44,10 +44,9 @@
 
 clean-local::
 	$(MAKE) -C swoc clean
-endif
 
-if EXPORT_SWOC_HEADERS
 install-data-local::
 	$(MAKE) -C swoc install
+
 endif
 
diff --git a/lib/perl/MANIFEST b/lib/perl/MANIFEST
index 51aeb64..f8a23d6 100644
--- a/lib/perl/MANIFEST
+++ b/lib/perl/MANIFEST
@@ -2,8 +2,6 @@
 README
 examples/forw_proxy_conf.pl
 MANIFEST
-t/Apache-TS-AdminClient.t
 lib/Apache/TS.pm
 lib/Apache/TS/Config/Records.pm
-lib/Apache/TS/AdminClient.pm
 lib/Apache/TS/Config.pm
diff --git a/lib/perl/README b/lib/perl/README
index 0b10953..766abd9 100644
--- a/lib/perl/README
+++ b/lib/perl/README
@@ -4,9 +4,6 @@
 Apache::TS - a set of perl interfaces to manage an Apache Traffic Server
              instance. This includes the following sub-modules
 
-    Apache::TS::AdminClient - access the statistics and configuration
-     			      settings stored within Apache Traffic Server
-
     Apache::TS::Config - Manage Apache Traffic Server configs
 
     Apache::TS::Config::Records - Manage records.config settings.
diff --git a/lib/perl/lib/Apache/TS.pm.in b/lib/perl/lib/Apache/TS.pm.in
index f492fea..e6ca7bb 100644
--- a/lib/perl/lib/Apache/TS.pm.in
+++ b/lib/perl/lib/Apache/TS.pm.in
@@ -50,17 +50,19 @@
 =head1 SYNOPSIS
 
   #!/usr/bin/perl
-  use Apache::TS::AdminClient;
+  use Apache::TS::Config::Records;
 
-  my $cli = Apache::TS::AdminClient->new(%input);
-  my $string = $cli->get_stat("proxy.config.product_company");
-  print "$string\n";
+  my $recedit = new Apache::TS::Config::Records(file => "etc/trafficserver/records.config.default");
+  # Threads
+  $recedit->set(conf => "proxy.config.exec_thread.autoconfig", val => "0");
+  # ...
+  # Write it all out
+  $recedit->write(file => "etc/trafficserver/records.config");
 
 
 =head1 DESCRIPTION
 
 This is the main module for Apache::TS, in includes the following sub-modules
-  Apache::TS::AdminClient
   Apache::TS::Config
   Apache::TS::Config::Records
 
diff --git a/lib/perl/lib/Apache/TS/AdminClient.pm b/lib/perl/lib/Apache/TS/AdminClient.pm
deleted file mode 100644
index b097c00..0000000
--- a/lib/perl/lib/Apache/TS/AdminClient.pm
+++ /dev/null
@@ -1,627 +0,0 @@
-#
-# 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.
-
-package Apache::TS::AdminClient;
-
-use warnings;
-use strict;
-
-require 5.006;
-
-use Carp;
-use IO::Socket::UNIX;
-use IO::Select;
-
-use Apache::TS;
-
-# Mgmt API command constants, should track ts/mgmtapi.h
-use constant {
-    TS_RECORD_SET                 => 0,
-    TS_RECORD_GET                 => 1,
-    TS_PROXY_STATE_GET            => 2,
-    TS_PROXY_STATE_SET            => 3,
-    TS_RECONFIGURE                => 4,
-    TS_RESTART                    => 5,
-    TS_BOUNCE                     => 6,
-    TS_EVENT_RESOLVE              => 7,
-    TS_EVENT_GET_MLT              => 8,
-    TS_EVENT_ACTIVE               => 9,
-    TS_EVENT_REG_CALLBACK         => 10,
-    TS_EVENT_UNREG_CALLBACK       => 11,
-    TS_EVENT_NOTIFY               => 12,
-    TS_STATS_RESET_NODE           => 13,
-    TS_STORAGE_DEVICE_CMD_OFFLINE => 14,
-    TS_RECORD_MATCH_GET           => 15,
-    TS_API_PING                   => 16,
-    TS_SERVER_BACKTRACE           => 17,
-    TS_RECORD_DESCRIBE_CONFIG     => 18,
-    TS_LIFECYCLE_MESSAGE          => 19,
-    TS_UNDEFINED_OP               => 20
-};
-
-use constant {
-    TS_REC_INT     => 0,
-    TS_REC_COUNTER => 1,
-    TS_REC_FLOAT   => 2,
-    TS_REC_STRING  => 3
-};
-
-use constant {
-    TS_ERR_OKAY                => 0,
-    TS_ERR_READ_FILE           => 1,
-    TS_ERR_WRITE_FILE          => 2,
-    TS_ERR_PARSE_CONFIG_RULE   => 3,
-    TS_ERR_INVALID_CONFIG_RULE => 4,
-    TS_ERR_NET_ESTABLISH       => 5,
-    TS_ERR_NET_READ            => 6,
-    TS_ERR_NET_WRITE           => 7,
-    TS_ERR_NET_EOF             => 8,
-    TS_ERR_NET_TIMEOUT         => 9,
-    TS_ERR_SYS_CALL            => 10,
-    TS_ERR_PARAMS              => 11,
-    TS_ERR_FAIL                => 12
-};
-
-# Semi-intelligent way of finding the mgmtapi socket.
-sub _find_socket
-{
-    my $path = shift || "";
-    my $name = shift || "mgmtapi.sock";
-    my @sockets_def = (
-        $path,                          Apache::TS::PREFIX . '/' . Apache::TS::REL_RUNTIMEDIR . '/' . 'mgmtapi.sock',
-        '/usr/local/var/trafficserver', '/usr/local/var/run/trafficserver',
-        '/usr/local/var/run',           '/var/trafficserver',
-        '/var/run/trafficserver',       '/var/run',
-        '/opt/ats/var/trafficserver',
-    );
-
-    foreach my $socket (@sockets_def) {
-        return $socket if (-S $socket);
-        return "${socket}/${name}" if (-S "${socket}/${name}");
-
-    }
-    return undef;
-}
-
-#
-# Constructor
-#
-sub new
-{
-    my ($class, %args) = @_;
-    my $self = {};
-
-    $self->{_socket_path} = _find_socket($args{socket_path});
-    $self->{_socket}      = undef;
-    croak "Unable to locate socket, please pass socket_path with the management api socket location to Apache::TS::AdminClient"
-      if (!$self->{_socket_path});
-    if ((!-r $self->{_socket_path}) or (!-w $self->{_socket_path}) or (!-S $self->{_socket_path})) {
-        croak "Unable to open $self->{_socket_path} for reads or writes";
-    }
-
-    $self->{_select} = IO::Select->new();
-    bless $self, $class;
-
-    $self->open_socket();
-
-    return $self;
-}
-
-#
-# Destructor
-#
-sub DESTROY
-{
-    my $self = shift;
-    return $self->close_socket();
-}
-
-#
-# Open the socket (Unix domain)
-#
-sub open_socket
-{
-    my $self = shift;
-    my %args = @_;
-
-    if (defined($self->{_socket})) {
-        if ($args{force} || $args{reopen}) {
-            $self->close_socket();
-        } else {
-            return undef;
-        }
-    }
-
-    $self->{_socket} = IO::Socket::UNIX->new(
-        Type => SOCK_STREAM,
-        Peer => $self->{_socket_path}
-    ) or croak("Error opening socket - $@");
-
-    return undef unless defined($self->{_socket});
-    $self->{_select}->add($self->{_socket});
-
-    return $self;
-}
-
-sub close_socket
-{
-    my $self = shift;
-
-    # if socket doesn't exist, return as there's nothing to do.
-    return unless defined($self->{_socket});
-
-    # gracefully close socket.
-    $self->{_select}->remove($self->{_socket});
-    $self->{_socket}->close();
-    $self->{_socket} = undef;
-
-    return $self;
-}
-
-#
-# Do reads()'s on our Unix domain socket, takes an optional timeout, in ms's.
-#
-sub _do_read
-{
-    my $self    = shift;
-    my $timeout = shift || 1 / 1000.0;    # 1ms by default
-    my $res     = "";
-
-    while ($self->{_select}->can_read($timeout)) {
-        my $rc = $self->{_socket}->sysread($res, 1024, length($res));
-
-        # If the server dies we get into a infinite loop because
-        # IO::Select::can_read keeps returning true
-        # In this condition sysread returns 0 or undef
-        # Also, we want to return an undef rather than a partial response
-        # to avoid unmarshalling errors in the callers
-        if (!defined($rc) || ($rc == 0)) {
-            $res = undef;
-            last;
-        }
-    }
-
-    return $res || undef;
-}
-
-#
-# Get (read) a stat out of the local manager. Note that the assumption is
-# that you are calling this with an existing stats "name".
-#
-sub get_stat
-{
-    my ($self, $stat) = @_;
-    my $res = "";
-
-    return undef unless defined($self->{_socket});
-    return undef unless $self->{_select}->can_write(10);
-
-    # This is a total hack for now, we need to wrap this into the proper mgmt API library.
-    # The request format is:
-    #   MGMT_MARSHALL_INT: message length
-    #   MGMT_MARSHALL_INT: TS_RECORD_GET
-    #   MGMT_MARSHALL_STRING: record name
-    my $msg = pack("ll/Z", TS_RECORD_GET, $stat);
-    $self->{_socket}->print(pack("l/a", $msg));
-    $res = $self->_do_read();
-    return undef unless defined($res);    # Don't proceed on read failure.
-
-    # The response format is:
-    #   MGMT_MARSHALL_INT: message length
-    #   MGMT_MARSHALL_INT: error code
-    #   MGMT_MARSHALL_INT: record class
-    #   MGMT_MARSHALL_INT: record type
-    #   MGMT_MARSHALL_STRING: record name
-    #   MGMT_MARSHALL_DATA: record data
-    ($msg) = unpack("l/a", $res);
-    my ($ecode, $rclass, $type, $name, $value) = unpack("l l l l/Z l/a", $msg);
-
-    if ($ecode == TS_ERR_OKAY) {
-        if ($type == TS_REC_INT || $type == TS_REC_COUNTER) {
-            my ($ival) = unpack("q", $value);
-            return $ival;
-        } elsif ($type == TS_REC_FLOAT) {
-            my ($fval) = unpack("f", $value);
-            return $fval;
-        } elsif ($type == TS_REC_STRING) {
-            my ($sval) = unpack("Z*", $value);
-            return $sval;
-        }
-    }
-
-    return undef;
-}
-*get_config = \&get_stat;
-
-1;
-
-__END__
-
-#-=-=-=-=-=-=-=-= Give us some POD please =-=-=-=-=-=-=-=-
-
-=head1 NAME:
-
-Apache::TS::AdminClient - a perl interface to the statistics and configuration settings stored within Apache Traffic Server.
-
-=head1 SYNOPSIS
-
-  #!/usr/bin/perl
-  use Apache::TS::AdminClient;
-
-  my $cli = Apache::TS::AdminClient->new(%input);
-  my $string = $cli->get_stat("proxy.config.product_company");
-  print "$string\n";
-
-
-=head1 DESCRIPTION:
-
-AdminClient opens a TCP connection to a unix domain socket on local disk.  When the connection is established,
-AdminClient will write requests to the socket and wait for Apache Traffic Server to return a response.  Valid
-request strings can be found in RecordsConfig.cc which is included with Apache Traffic Server source.
-A list of valid request strings are included with this documentation, but this included list may not be complete
-as future releases of Apache Traffic Server may include new request strings or remove existing ones.
-
-=head1 CONSTRUCTOR
-
-When the object is created for this module, it assumes the 'Unix Domain Socket' is at the default location from
-the Apache Traffic Server installation. This can be changed when creating the object by setting B<'socket_path'>.
-For example:
-
-=over 4
-
-=item my $cli = AdminClient->new(socket_path=> "/var/trafficserver");
-
-
-This would make the module look for the 'Unix Domain Socket' in the directory '/var/trafficserver'. The path
-can optionally include the name of the Socket file, without it the constructor defaults to 'mgmtapi.sock'.
-
-=back
-
-=head1 PUBLIC METHODS
-
-To read a single metric (or configuration), two APIs are available:
-
-=over 4
-
-=item $cli->get_stat($stats_name);
-
-=item $cli->get_config($config_name);
-
-This will return a (scalar) value for this metric or configuration.
-
-=back
-
-=head1 traffic_ctl
-
-There is a command line tool included with Apache Traffic Server called traffic_ctl which overlaps with this module.  traffic_ctl
-can be used to read and write statistics or config settings that this module can.  Hence if you don't want to write a perl one-liner to
-get to this information, traffic_ctl is your tool.
-
-=head1 List of configurations
-
-The Apache Traffic Server Administration Manual will explain what these strings represent.  (https://docs.trafficserver.apache.org/en/latest/)
-
- proxy.config.accept_threads
- proxy.config.task_threads
- proxy.config.admin.synthetic_port
- proxy.config.admin.cli_path
- proxy.config.admin.user_id
- proxy.config.alarm.abs_path
- proxy.config.alarm.bin
- proxy.config.alarm.script_runtime
- proxy.config.bandwidth_mgmt.filename
- proxy.config.bin_path
- proxy.config.body_factory.enable_customizations
- proxy.config.body_factory.enable_logging
- proxy.config.body_factory.response_max_size
- proxy.config.body_factory.response_suppression_mode
- proxy.config.body_factory.template_sets_dir
- proxy.config.cache.agg_write_backlog
- proxy.config.cache.alt_rewrite_max_size
- proxy.config.cache.control.filename
- proxy.config.cache.dir.sync_frequency
- proxy.config.cache.enable_checksum
- proxy.config.cache.enable_read_while_writer
- proxy.config.cache.hostdb.disable_reverse_lookup
- proxy.config.cache.hostdb.sync_frequency
- proxy.config.cache.hosting_filename
- proxy.config.cache.ip_allow.filename
- proxy.config.cache.limits.http.max_alts
- proxy.config.cache.log.alternate.eviction
- proxy.config.cache.max_disk_errors
- proxy.config.cache.max_doc_size
- proxy.config.cache.min_average_object_size
- proxy.config.cache.volume_filename
- proxy.config.cache.permit.pinning
- proxy.config.cache.ram_cache_cutoff
- proxy.config.cache.ram_cache.size
- proxy.config.cache.select_alternate
- proxy.config.cache.threads_per_disk
- proxy.config.cache.mutex_retry_delay
- proxy.config.cop.core_signal
- proxy.config.cop.linux_min_memfree_kb
- proxy.config.cop.linux_min_swapfree_kb
- proxy.config.core_limit
- proxy.config.diags.action.enabled
- proxy.config.diags.action.tags
- proxy.config.diags.debug.enabled
- proxy.config.diags.debug.tags
- proxy.config.diags.output.alert
- proxy.config.diags.output.debug
- proxy.config.diags.output.diag
- proxy.config.diags.output.emergency
- proxy.config.diags.output.error
- proxy.config.diags.output.fatal
- proxy.config.diags.output.note
- proxy.config.diags.output.status
- proxy.config.diags.output.warning
- proxy.config.diags.show_location
- proxy.config.dns.failover_number
- proxy.config.dns.failover_period
- proxy.config.dns.lookup_timeout
- proxy.config.dns.max_dns_in_flight
- proxy.config.dns.nameservers
- proxy.config.dns.resolv_conf
- proxy.config.dns.retries
- proxy.config.dns.round_robin_nameservers
- proxy.config.dns.search_default_domains
- proxy.config.dns.splitDNS.enabled
- proxy.config.dns.splitdns.filename
- proxy.config.dump_mem_info_frequency
- proxy.config.env_prep
- proxy.config.exec_thread.autoconfig
- proxy.config.exec_thread.autoconfig.scale
- proxy.config.exec_thread.limit
- proxy.config.header.parse.no_host_url_redirect
- proxy.config.hostdb
- proxy.config.hostdb.fail.timeout
- proxy.config.hostdb.filename
- proxy.config.hostdb.lookup_timeout
- proxy.config.hostdb.migrate_on_demand
- proxy.config.hostdb.re_dns_on_reload
- proxy.config.hostdb.serve_stale_for
- proxy.config.hostdb.size
- proxy.config.hostdb.storage_path
- proxy.config.hostdb.storage_size
- proxy.config.hostdb.strict_round_robin
- proxy.config.hostdb.timeout
- proxy.config.hostdb.ttl_mode
- proxy.config.hostdb.verify_after
- proxy.config.http.accept_encoding_filter.filename
- proxy.config.http.accept_no_activity_timeout
- proxy.config.http.insert_client_ip
- proxy.config.http.anonymize_other_header_list
- proxy.config.http.anonymize_remove_client_ip
- proxy.config.http.anonymize_remove_cookie
- proxy.config.http.anonymize_remove_from
- proxy.config.http.anonymize_remove_referer
- proxy.config.http.anonymize_remove_user_agent
- proxy.config.http.background_fill_active_timeout
- proxy.config.http.background_fill_completed_threshold
- proxy.config.http.cache.cache_responses_to_cookies
- proxy.config.http.cache.cache_urls_that_look_dynamic
- proxy.config.http.cache.guaranteed_max_lifetime
- proxy.config.http.cache.guaranteed_min_lifetime
- proxy.config.http.cache.heuristic_lm_factor
- proxy.config.http.cache.heuristic_max_lifetime
- proxy.config.http.cache.heuristic_min_lifetime
- proxy.config.http.cache.http
- proxy.config.http.cache.ignore_accept_charset_mismatch
- proxy.config.http.cache.ignore_accept_encoding_mismatch
- proxy.config.http.cache.ignore_accept_language_mismatch
- proxy.config.http.cache.ignore_accept_mismatch
- proxy.config.http.cache.ignore_authentication
- proxy.config.http.cache.ignore_client_cc_max_age
- proxy.config.http.cache.ignore_client_no_cache
- proxy.config.http.cache.ignore_server_no_cache
- proxy.config.http.cache.ims_on_client_no_cache
- proxy.config.http.cache.max_open_read_retries
- proxy.config.http.cache.max_open_write_retries
- proxy.config.http.cache.max_stale_age
- proxy.config.http.cache.open_read_retry_time
- proxy.config.http.cache.range.lookup
- proxy.config.http.cache.range.write
- proxy.config.http.cache.required_headers
- proxy.config.http.cache.when_to_revalidate
- proxy.config.http.chunking_enabled
- proxy.config.http.connect_attempts_max_retries
- proxy.config.http.connect_attempts_max_retries_dead_server
- proxy.config.http.connect_attempts_rr_retries
- proxy.config.http.connect_attempts_timeout
- proxy.config.http.connect_ports
- proxy.config.http.default_buffer_size
- proxy.config.http.default_buffer_water_mark
- proxy.config.http.doc_in_cache_skip_dns
- proxy.config.http.down_server.cache_time
- proxy.config.http.enabled
- proxy.config.http.enable_http_info
- proxy.config.http.enable_http_stats
- proxy.config.http.errors.log_error_pages
- proxy.config.http.forward.proxy_auth_to_parent
- proxy.config.http.global_user_agent_header
- proxy.config.http.insert_age_in_response
- proxy.config.http.insert_request_via_str
- proxy.config.http.insert_response_via_str
- proxy.config.http.insert_squid_x_forwarded_for
- proxy.config.http.keep_alive_enabled_in
- proxy.config.http.keep_alive_enabled_out
- proxy.config.http.keep_alive_no_activity_timeout_in
- proxy.config.http.keep_alive_no_activity_timeout_out
- proxy.config.http.keep_alive_post_out
- proxy.config.http.negative_caching_enabled
- proxy.config.http.negative_caching_list
- proxy.config.http.negative_caching_lifetime
- proxy.config.http.negative_revalidating_enabled
- proxy.config.http.negative_revalidating_lifetime
- proxy.config.http.no_dns_just_forward_to_parent
- proxy.config.http.no_origin_server_dns
- proxy.config.http.normalize_ae_gzip
- proxy.config.http.number_of_redirections
- proxy.config.http.per_server.connection.max
- proxy.config.http.origin_min_keep_alive_connections
- proxy.config.http.parent_proxies
- proxy.config.http.parent_proxy.connect_attempts_timeout
- proxy.config.http.parent_proxy.fail_threshold
- proxy.config.http.parent_proxy.file
- proxy.config.http.parent_proxy.per_parent_connect_attempts
- proxy.config.http.parent_proxy.retry_time
- proxy.config.http.parent_proxy.total_connect_attempts
- proxy.config.http.post_connect_attempts_timeout
- proxy.config.http.post_copy_size
- proxy.config.http.push_method_enabled
- proxy.config.http.quick_filter.mask
- proxy.config.http.record_heartbeat
- proxy.config.http.referer_default_redirect
- proxy.config.http.referer_filter
- proxy.config.http.referer_format_redirect
- proxy.config.http.request_header_max_size
- proxy.config.http.request_via_str
- proxy.config.http.response_header_max_size
- proxy.config.http.response_server_enabled
- proxy.config.http.response_server_str
- proxy.config.http.response_via_str
- proxy.config.http.send_http11_requests
- proxy.config.http.server_max_connections
- proxy.config.http.server_port
- proxy.config.http.slow.log.threshold
- proxy.config.http.connect_ports
- proxy.config.http.transaction_active_timeout_in
- proxy.config.http.transaction_active_timeout_out
- proxy.config.http.transaction_no_activity_timeout_in
- proxy.config.http.transaction_no_activity_timeout_out
- proxy.config.http_ui_enabled
- proxy.config.http.uncacheable_requests_bypass_parent
- proxy.config.io.max_buffer_size
- proxy.config.lm.pserver_timeout_msecs
- proxy.config.lm.pserver_timeout_secs
- proxy.config.local_state_dir
- proxy.config.log.ascii_buffer_size
- proxy.config.log.auto_delete_rolled_files
- proxy.config.log.file_stat_frequency
- proxy.config.log.hostname
- proxy.config.log.log_buffer_size
- proxy.config.log.logfile_dir
- proxy.config.log.logfile_perm
- proxy.config.log.logging_enabled
- proxy.config.log.max_line_size
- proxy.config.log.max_secs_per_buffer
- proxy.config.log.max_space_mb_for_logs
- proxy.config.log.max_space_mb_headroom
- proxy.config.log.overspill_report_count
- proxy.config.log.rolling_enabled
- proxy.config.log.rolling_interval_sec
- proxy.config.log.rolling_offset_hr
- proxy.config.log.rolling_size_mb
- proxy.config.log.sampling_frequency
- proxy.config.log.space_used_frequency
- proxy.config.log.config.filename
- proxy.config.manager_binary
- proxy.config.net.connections_throttle
- proxy.config.net.listen_backlog
- proxy.config.net.sock_mss_in
- proxy.config.net.sock_option_flag_in
- proxy.config.net.sock_option_flag_out
- proxy.config.net.sock_recv_buffer_size_in
- proxy.config.net.sock_recv_buffer_size_out
- proxy.config.net.sock_send_buffer_size_in
- proxy.config.net.sock_send_buffer_size_out
- proxy.config.net.defer_accept
- proxy.config.output.logfile
- proxy.config.plugin.plugin_dir
- proxy.config.process_manager.mgmt_port
- proxy.config.process_manager.timeout
- proxy.config.product_company
- proxy.config.product_name
- proxy.config.product_vendor
- proxy.config.proxy.authenticate.basic.realm
- proxy.config.proxy_binary
- proxy.config.proxy_binary_opts
- proxy.config.proxy_name
- proxy.config.remap.num_remap_threads
- proxy.config.res_track_memory
- proxy.config.reverse_proxy.enabled
- proxy.config.reverse_proxy.oldasxbehavior
- proxy.config.socks.accept_enabled
- proxy.config.socks.accept_port
- proxy.config.socks.connection_attempts
- proxy.config.socks.default_servers
- proxy.config.socks.http_port
- proxy.config.socks.per_server_connection_attempts
- proxy.config.socks.server_connect_timeout
- proxy.config.socks.server_fail_threshold
- proxy.config.socks.server_retry_time
- proxy.config.socks.server_retry_timeout
- proxy.config.socks.socks_config_file
- proxy.config.socks.socks_needed
- proxy.config.socks.socks_timeout
- proxy.config.socks.socks_version
- proxy.config.srv_enabled
- proxy.config.ssl.CA.cert.filename
- proxy.config.ssl.CA.cert.path
- proxy.config.ssl.client.CA.cert.filename
- proxy.config.ssl.client.CA.cert.path
- proxy.config.ssl.client.cert.filename
- proxy.config.ssl.client.certification_level
- proxy.config.ssl.client.cert.path
- proxy.config.ssl.client.private_key.filename
- proxy.config.ssl.client.private_key.path
- proxy.config.ssl.server.cert_chain.filename
- proxy.config.ssl.server.cert.path
- proxy.config.ssl.server.cipher_suite
- proxy.config.ssl.server.honor_cipher_order
- proxy.config.ssl.server.dhparams_file
- proxy.config.ssl.TLSv1
- proxy.config.ssl.TLSv1_1
- proxy.config.ssl.TLSv1_2
- proxy.config.ssl.TLSv1_3
- proxy.config.ssl.server.multicert.filename
- proxy.config.ssl.server.private_key.path
- proxy.config.ssl.keylog_file
- proxy.config.stat_collector.interval
- proxy.config.stat_collector.port
- proxy.config.syslog_facility
- proxy.config.system.file_max_pct
- proxy.config.thread.default.stacksize
- proxy.config.udp.free_cancelled_pkts_sec
- proxy.config.udp.periodic_cleanup
- proxy.config.udp.send_retries
- proxy.config.url_remap.filename
- proxy.config.url_remap.pristine_host_hdr
- proxy.config.url_remap.remap_required
-
-=head1 LICENSE
-
- Simple Apache Traffic Server client object, to communicate with the local manager.
-
- 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.
-
-=cut
-
-#-=-=-=-=-=-=-=-= No more POD for you =-=-=-=-=-=-=-=-
diff --git a/lib/perl/t/Apache-TS-AdminClient.t b/lib/perl/t/Apache-TS-AdminClient.t
deleted file mode 100644
index 53aeace..0000000
--- a/lib/perl/t/Apache-TS-AdminClient.t
+++ /dev/null
@@ -1,35 +0,0 @@
-# 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.
-
-# Before `make install' is performed this script should be runnable with
-# `make test'. After `make install' it should work as `perl Apache-TS-AdminClient.t'
-
-#########################
-
-# change 'tests => 1' to 'tests => last_test_to_print';
-
-
-use Test::More tests => 2;
-BEGIN { use_ok('Apache::TS::AdminClient') };
-
-#########################
-
-# Insert your test code below, the Test::More module is use()ed here so read
-# its man page ( perldoc Test::More ) for help writing this test script.
-
-#----- is this right or do we need to use Test::MockObject as well?
-our @methods = qw(new DESTROY open_socket close_socket get_stat);
-can_ok('Apache::TS::AdminClient', @methods);
diff --git a/lib/swoc/Makefile.am b/lib/swoc/Makefile.am
index dff7cb9..b928917 100644
--- a/lib/swoc/Makefile.am
+++ b/lib/swoc/Makefile.am
@@ -16,19 +16,17 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-include $(top_srcdir)/build/tidy.mk
-
 lib_LTLIBRARIES = libtsswoc.la
 
 library_includedir=$(includedir)/swoc
 
-AM_CPPFLAGS += -I$(abs_top_srcdir)/include -I$(abs_top_srcdir)/lib/swoc/include
+AM_CPPFLAGS += @SWOC_INCLUDES@
 
-libtsswoc_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_VERSION@
-
+libtsswoc_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -release 1.4.0
 libtsswoc_la_SOURCES = \
-	src/ArenaWriter.cc  src/bw_format.cc  src/bw_ip_format.cc  src/Errata.cc  src/MemArena.cc  src/RBTree.cc  src/swoc_file.cc  src/swoc_ip.cc  src/TextView.cc
+	src/ArenaWriter.cc  src/bw_format.cc  src/bw_ip_format.cc  src/Errata.cc  src/MemArena.cc  src/RBTree.cc  src/swoc_file.cc  src/swoc_ip.cc  src/TextView.cc src/string_view_util.cc
 
+if EXPORT_SWOC_HEADERS
 library_include_HEADERS = \
         include/swoc/ArenaWriter.h \
         include/swoc/BufferWriter.h \
@@ -41,6 +39,10 @@
         include/swoc/Errata.h \
         include/swoc/IntrusiveDList.h \
         include/swoc/IntrusiveHashMap.h \
+        include/swoc/IPAddr.h \
+        include/swoc/IPEndpoint.h \
+        include/swoc/IPRange.h \
+        include/swoc/IPSrv.h \
         include/swoc/Lexicon.h \
         include/swoc/MemArena.h \
         include/swoc/MemSpan.h \
@@ -50,9 +52,11 @@
         include/swoc/swoc_ip.h \
         include/swoc/swoc_meta.h \
         include/swoc/swoc_version.h\
+        include/swoc/string_view_util.h \
         include/swoc/TextView.h \
         include/swoc/Vectray.h \
         include/swoc/HashFNV.h
+endif
 
 clean-local:
 
diff --git a/lib/swoc/include/swoc/DiscreteRange.h b/lib/swoc/include/swoc/DiscreteRange.h
index 503911c..ee306a9 100644
--- a/lib/swoc/include/swoc/DiscreteRange.h
+++ b/lib/swoc/include/swoc/DiscreteRange.h
@@ -22,36 +22,75 @@
 /// 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.
 /// @{
+
+/** Maximum value.
+ *
+ * @tparam M Metric type.
+ * @return Maximum value for @a M.
+ *
+ * Use @c std::numeric_limits.
+ */
 template <typename M>
 constexpr auto
 maximum(meta::CaseTag<0>) -> M {
   return std::numeric_limits<M>::max();
 }
 
+/** Maximum value.
+ *
+ * @tparam M Metric type.
+ * @return Maximum value for @a M.
+ *
+ * Use @c M::MAX
+ */
 template <typename M>
 constexpr auto
 maximum(meta::CaseTag<1>) -> decltype(M::MAX) {
   return M::MAX;
 }
 
+/** Maximum value.
+ *
+ * @tparam M Metric type.
+ * @return Maximum value for @a M.
+ */
 template <typename M>
 constexpr M
 maximum() {
   return maximum<M>(meta::CaseArg);
 }
 
+/** Minimum value.
+ *
+ * @tparam M Metric type.
+ * @return Minimum value for @a M
+ *
+ * Use @c std::numeric_limits
+ */
 template <typename M>
 constexpr auto
 minimum(meta::CaseTag<0>) -> M {
   return std::numeric_limits<M>::min();
 }
 
+/** Minimum value.
+ *
+ * @tparam M Metric type.
+ * @return Minimum value for @a M
+ *
+ * Use @c M::MIN
+ */
 template <typename M>
 constexpr auto
 minimum(meta::CaseTag<1>) -> decltype(M::MIN) {
   return M::MIN;
 }
 
+/** Minimum value.
+ *
+ * @tparam M Metric type.
+ * @return Minimum value for @a M
+ */
 template <typename M>
 constexpr M
 minimum() {
@@ -773,34 +812,27 @@
   /** 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.
+   * @return An iterator for the item or the @c end iterator if not.
    */
   iterator find(METRIC const &metric);
 
+  /** Find the payload at @a metric.
+   *
+   * @param metric The metric for which to search.
+   * @return An iterator for the item or the @c end iterator if not.
+   */
+  const_iterator find(METRIC const &metric) const;
+
   /// @return The number of distinct ranges.
   size_t count() const;
 
-  iterator
-  begin() {
-    return _list.begin();
-  }
-
-  iterator
-  end() {
-    return _list.end();
-  }
+  iterator begin() { return _list.begin(); }
+  iterator end() { return _list.end(); }
+  const_iterator begin() const { return _list.begin(); }
+  const_iterator end() const { 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();
-  }
+  void clear();
 
 protected:
   /** Find the lower bound range for @a target.
@@ -925,6 +957,13 @@
 
 template <typename METRIC, typename PAYLOAD>
 auto
+DiscreteSpace<METRIC, PAYLOAD>::find(METRIC const &metric) const -> const_iterator
+{
+  return const_cast<self_type *>(this)->find(metric);
+}
+
+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.
@@ -1480,4 +1519,17 @@
   return *this;
 }
 
+template <typename METRIC, typename PAYLOAD>
+void
+DiscreteSpace<METRIC, PAYLOAD>::clear()
+{
+  for (auto &node : _list) {
+    std::destroy_at(&node.payload());
+  }
+  _list.clear();
+  _root = nullptr;
+  _arena.clear();
+  _fa.clear();
+}
+
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/Errata.h b/lib/swoc/include/swoc/Errata.h
index 11675f5..77b20b2 100644
--- a/lib/swoc/include/swoc/Errata.h
+++ b/lib/swoc/include/swoc/Errata.h
@@ -60,6 +60,9 @@
   using code_type     = std::error_code; ///< Type for message code.
   using severity_type = uint8_t;         ///< Underlying type for @c Severity.
 
+  /// Severity value for an instance.
+  /// This provides conversion to a numeric value, but not from. The result is constructors must be
+  /// passed an explicit serverity, avoiding ambiguity with other possible numeric arguments.
   struct Severity {
     severity_type _raw; ///< Severity numeric value
 
@@ -142,6 +145,8 @@
     /** Construct with @a text.
      *
      * @param text Annotation content (literal).
+     * @param severity Local severity.
+     * @param level Nesting level.
      *
      * @a text is presumed to be stable for the @c Annotation lifetime - this constructor simply copies
      * the view.
@@ -280,6 +285,7 @@
   template <typename... Args> self_type &note_v(std::string_view fmt, std::tuple<Args...> const &args);
 
   /** Append an @c Annotation.
+   * @param severity Local severity.
    * @param fmt Format string (@c BufferWriter style).
    * @param args Arguments for values in @a fmt.
    * @return A reference to this object.
@@ -1225,6 +1231,7 @@
 
 // Tuple / structured binding support.
 namespace std {
+/// @cond INTERNAL_DETAIL
 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>> {
@@ -1238,16 +1245,17 @@
 };
 
 template <typename R> class tuple_size<swoc::Rv<R>> : public std::integral_constant<size_t, 2> {};
-
+/// @endcond
 } // namespace std
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 // Not sure how much of this is needed, but experimentally all of these were needed in one
 // use case or another of structured binding. I wasn't able to make this work if this was
 // defined in namespace @c std. Also, because functions can't be partially specialized, it is
-// necessary to use @c constexpr @c if to handle the case. This should roll up nicely when
+// necessary to use @c constexpr @c if to handle the cases. This should roll up nicely when
 // compiled.
 
+/// @cond INTERNAL_DETAIL
 template <size_t IDX, typename R>
 typename std::tuple_element<IDX, swoc::Rv<R>>::type &
 get(swoc::Rv<R> &&rv) {
@@ -1283,5 +1291,5 @@
   // Shouldn't need this due to the @c static_assert but the Intel compiler requires it.
   throw std::domain_error("Errata index value out of bounds");
 }
-
+/// @endcond
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/IPAddr.h b/lib/swoc/include/swoc/IPAddr.h
new file mode 100644
index 0000000..cacc455
--- /dev/null
+++ b/lib/swoc/include/swoc/IPAddr.h
@@ -0,0 +1,1371 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Network Geographics 2014
+/** @file
+   IP address and network related classes.
+ */
+
+#pragma once
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include "swoc/swoc_version.h"
+#include "swoc/swoc_meta.h"
+#include "swoc/MemSpan.h"
+
+namespace swoc { inline namespace SWOC_VERSION_NS {
+
+using std::string_view;
+
+union IPEndpoint;
+class IPAddr;
+class IPMask;
+
+/** Storage for an IPv4 address.
+    Stored in host order.
+ */
+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 constexpr size_t WIDTH = std::numeric_limits<unsigned char>::digits * SIZE; ///< # of bits in an address.
+
+  static const self_type MIN;                                                        ///< Minimum value.
+  static const self_type MAX;                                                        ///< Maximum value.
+  static constexpr sa_family_t AF_value = AF_INET;                                   ///< Address family type.
+
+  constexpr IP4Addr() = default; ///< Default constructor - ANY address.
+
+  /// Copy constructor.
+  IP4Addr(self_type const& that) = default;
+
+  /// Construct using IPv4 @a addr (in host order).
+  /// @note Host order seems odd, but all of the standard network macro values such as @c INADDR_LOOPBACK
+  /// are in host order.
+  explicit constexpr IP4Addr(in_addr_t addr);
+
+  /// Construct from @c sockaddr_in.
+  explicit IP4Addr(sockaddr_in const *s);
+
+  /// Construct from text representation.
+  /// If the @a text is invalid the result is an invalid instance.
+  IP4Addr(string_view const &text);
+
+  /// Self assignment.
+  self_type & operator=(self_type const& that) = default;
+
+  /// 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 *sa);
+
+  /// Increment address.
+  self_type &operator++();
+
+  /// Decrement address.
+  self_type &operator--();
+
+  /** Byte access.
+   *
+   * @param idx Byte index.
+   * @return The byte at @a idx in the address (network order).
+   *
+   * For convenience, this returns in "text order" of the octets.
+   */
+  uint8_t operator[](unsigned idx) const;
+
+  /// Apply @a mask to address, leaving the network portion.
+  self_type &operator&=(IPMask const &mask);
+
+  /// Apply @a mask to address, creating the broadcast address.
+  self_type &operator|=(IPMask const &mask);
+
+  /// Write this adddress and @a host_order_port to the sockaddr @a sa.
+  sockaddr_in *copy_to(sockaddr_in *sa, in_port_t port = 0) const;
+
+  /// @return The address in network order.
+  in_addr_t network_order() const;
+
+  /// @return The address in host order.
+  in_addr_t host_order() const;
+
+  /** Parse @a text as IPv4 address.
+      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 load(string_view const &text);
+
+  /// Standard ternary compare.
+  int cmp(self_type const &that) const;
+
+  /// Get the IP address family.
+  /// @return @c AF_INET
+  /// @note Useful primarily for template classes.
+  constexpr sa_family_t family() const;
+
+  /// @return @c true if this is the "any" address, @c false if not.
+  bool is_any() const;
+
+  /// @return @c true if this is a multicast address, @c false if not.
+  bool is_multicast() const;
+
+  /// @return @c true if this is a loopback address, @c false if not.
+  bool is_loopback() const;
+
+  /// @return @c true if the address is in the link local network.
+  bool is_link_local() const;
+
+  /// @return @c true if the address is private.
+  bool is_private() const;
+
+  /** Left shift.
+   *
+   * @param n Number of bits to shift left.
+   * @return @a this.
+   */
+  self_type &operator<<=(unsigned n);
+
+  /** Right shift.
+   *
+   * @param n Number of bits to shift right.
+   * @return @a this.
+   */
+  self_type &operator>>=(unsigned n);
+
+  /** Bitwise AND.
+   *
+   * @param that Source address.
+   * @return @a this.
+   *
+   * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that.
+   */
+  self_type &operator&=(self_type const &that);
+
+  /** Bitwise OR.
+   *
+   * @param that Source address.
+   * @return @a this.
+   *
+   * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that.
+   */
+  self_type &operator|=(self_type const &that);
+
+  /** Convert between network and host order.
+   *
+   * @param src Input address.
+   * @return @a src with the byte reversed.
+   *
+   * This performs the same computation as @c ntohl and @c htonl but is @c constexpr to be usable
+   * in situations those two functions are not.
+   */
+  constexpr static in_addr_t reorder(in_addr_t src);
+
+protected:
+  /// Access by bytes.
+  using bytes = std::array<uint8_t, 4>;
+
+  friend bool operator==(self_type const &, self_type const &);
+  friend bool operator!=(self_type const &, self_type const &);
+  friend bool operator<(self_type const &, self_type const &);
+  friend bool operator<=(self_type const &, self_type const &);
+
+  in_addr_t _addr = INADDR_ANY; ///< Address in host order.
+};
+
+/** Storage for an IPv6 address.
+    Internal storage is not necessarily network ordered.
+    @see network_order
+    @see copy_to
+ */
+class IP6Addr {
+  using self_type = IP6Addr; ///< Self reference type.
+
+  friend class IP6Range;
+  friend class IPMask;
+
+public:
+  static constexpr size_t WIDTH         = 128;                                          ///< Number of bits in the address.
+  static constexpr size_t SIZE          = WIDTH / std::numeric_limits<uint8_t>::digits; ///< Size of address in bytes.
+  static constexpr sa_family_t AF_value = AF_INET6;                                     ///< Address family type.
+
+  using quad_type                 = uint16_t;                 ///< Size of one segment of an IPv6 address.
+  static constexpr size_t N_QUADS = SIZE / sizeof(quad_type); ///< # of quads in an IPv6 address.
+  /// Number of bits per quad.
+  static constexpr size_t QUAD_WIDTH = std::numeric_limits<uint8_t>::digits * sizeof(quad_type);
+
+  /// Direct access type for the address.
+  /// Equivalent to the data type for data member @c s6_addr in @c in6_addr.
+  using raw_type = std::array<uint8_t, SIZE>;
+
+  /// Minimum value of an address.
+  static const self_type MIN;
+  /// Maximum value of an address.
+  static const self_type MAX;
+
+  IP6Addr()            = default; ///< Default constructor - ANY address.
+  IP6Addr(self_type const &that) = default;
+
+  /// Construct using IPv6 @a addr.
+  explicit IP6Addr(in6_addr const &addr);
+
+  /// Construct from @c sockaddr_in.
+  explicit IP6Addr(sockaddr_in6 const *addr) { *this = addr; }
+
+  /// Construct from text representation.
+  /// If the @a text is invalid the result is any address.
+  /// @see load
+  IP6Addr(string_view const &text);
+
+  /** Construct mapped IPv4 address.
+   *
+   * @param addr IPv4 address
+   */
+  explicit IP6Addr(IP4Addr addr);
+
+  /// Self assignment.
+  self_type & operator=(self_type const& that) = default;
+
+  /** Left shift.
+   *
+   * @param n Number of bits to shift left.
+   * @return @a this.
+   */
+  self_type &operator<<=(unsigned n);
+
+  /** Right shift.
+   *
+   * @param n Number of bits to shift right.
+   * @return @a this.
+   */
+  self_type &operator>>=(unsigned n);
+
+  /** Bitwise AND.
+   *
+   * @param that Source address.
+   * @return @a this.
+   *
+   * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that.
+   */
+  self_type &operator&=(self_type const &that);
+
+  /** Bitwise OR.
+   *
+   * @param that Source address.
+   * @return @a this.
+   *
+   * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that.
+   */
+  self_type &operator|=(self_type const &that);
+
+  /// Increment address.
+  self_type &operator++();
+
+  /// Decrement address.
+  self_type &operator--();
+
+  /// Assign from IPv6 raw address.
+  self_type &operator=(in6_addr const &addr);
+
+  /// Set to the address in @a addr.
+  self_type &operator=(sockaddr_in6 const *addr);
+
+  /** Access a byte in the address.
+   *
+   * @param idx Byte index.
+   * @return The "text order" byte.
+   */
+  constexpr uint8_t operator [] (int idx) const;
+
+  /// Write to @c sockaddr using network order and @a host_order_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;
+
+  /// Return the address in network order.
+  in6_addr network_order() const;
+
+  /** Parse a string for an IP address.
+
+      The address resuling 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 load(string_view const &str);
+
+  /// Generic three value compare.
+  int cmp(self_type const &that) const;
+
+  /// @return The address family.
+  constexpr sa_family_t family() const;
+
+  /// @return @c true if this is the "any" address, @c false if not.
+  bool is_any() const;
+
+  /// @return @c true if this is a loopback address, @c false if not.
+  bool is_loopback() const;
+
+  /// @return @c true if this is a multicast address, @c false if not.
+  bool is_multicast() const;
+
+  /// @return @c true if this is a link local address, @c false if not.
+  bool is_link_local() const;
+
+  /// @return @c true if the address is private.
+  bool is_private() const;
+
+  ///  @return @c true if this is an IPv4 addressed mapped to IPv6, @c false if not.
+  bool is_mapped_ip4() const;
+
+  /** Reset to default constructed state.
+   *
+   * @return @a this
+   */
+  self_type & clear();
+
+  /** Bitwise AND.
+   *
+   * @param that Source mask.
+   * @return @a this.
+   *
+   * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that.
+   */
+  self_type &operator&=(IPMask const &that);
+
+  /** Bitwise OR.
+   *
+   * @param that Source mask.
+   * @return @a this.
+   *
+   * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that.
+   */
+  self_type &operator|=(IPMask const &that);
+
+  /** Convert between network and host ordering.
+   *
+   * @param dst Destination for re-ordered address.
+   * @param src Original address.
+   */
+  static void reorder(in6_addr &dst, raw_type const &src);
+
+  /** Convert between network and host ordering.
+   *
+   * @param dst Destination for re-ordered address.
+   * @param src Original address.
+   */
+  static void reorder(raw_type &dst, in6_addr const &src);
+
+  template < typename T > auto as_span() -> std::enable_if_t<swoc::meta::is_any_of_v<T, std::byte, uint8_t, uint16_t, uint32_t, uint64_t>, swoc::MemSpan<T>> {
+    return swoc::MemSpan(_addr._store).template rebind<T>();
+  }
+
+  template < typename T > auto as_span() const -> std::enable_if_t<swoc::meta::is_any_of_v<typename std::remove_const_t<T>, std::byte, uint8_t, uint16_t, uint32_t, uint64_t>, swoc::MemSpan<T const>> {
+    return swoc::MemSpan<uint64_t const>(_addr._store).template rebind<T const>();
+  }
+
+protected:
+  friend bool operator==(self_type const &, self_type const &);
+
+  friend bool operator!=(self_type const &, self_type const &);
+
+  friend bool operator<(self_type const &, self_type const &);
+
+  friend bool operator<=(self_type const &, self_type const &);
+
+  /// Direct access type for the address by quads (16 bits).
+  /// This corresponds to the elements of the text format of the address.
+  using quad_store_type = std::array<quad_type, N_QUADS>;
+
+  /// A bit mask of all 1 bits the size of a quad.
+  static constexpr quad_type QUAD_MASK = ~quad_type{0};
+
+  /// Type used as a "word", the natural working unit of the address.
+  using word_type = uint64_t;
+
+  static constexpr size_t WORD_SIZE = sizeof(word_type);
+
+  /// Number of bits per word.
+  static constexpr size_t WORD_WIDTH = std::numeric_limits<uint8_t>::digits * WORD_SIZE;
+
+  /// Number of words used for basic address storage.
+  static constexpr size_t N_STORE = SIZE / WORD_SIZE;
+
+  /// Type used to store the address.
+  using word_store_type = std::array<word_type, N_STORE>;
+
+  /// Type for digging around inside the address, with the various forms of access.
+  /// These are in sort of host order - @a _store elements are host order, but the
+  /// MSW and LSW are swapped (big-endian). This makes various bits of the implementation
+  /// easier. Conversion to and from network order is via the @c reorder method.
+  union {
+    word_store_type _store = {0}; ///< 0 is MSW, 1 is LSW.
+    quad_store_type _quad;        ///< By quad.
+    raw_type _raw;                ///< By byte.
+  } _addr;
+
+  static constexpr unsigned LSW = 1; ///< Least significant word index.
+  static constexpr unsigned MSW = 0; ///< Most significant word index.
+
+  /// Index of quads in @a _addr._quad.
+  /// This converts from the position in the text format to the quads in the binary format.
+  static constexpr std::array<unsigned, N_QUADS> QUAD_IDX = {3, 2, 1, 0, 7, 6, 5, 4};
+
+  /// Index of bytes in @a _addr._raw
+  /// This converts MSB (0) to LSB (15) indicies to the bytes in the binary format.
+  static constexpr std::array<unsigned, SIZE> RAW_IDX = { 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8 };
+
+  /// Convert between network and host order.
+  /// The conversion is symmetric.
+  /// @param dst Output where reordered value is placed.
+  /// @param src Input value to resorder.
+  static void reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]);
+
+  /** Construct from two 64 bit values.
+   *
+   * @param msw The most significant 64 bits, host order.
+   * @param lsw The least significant 64 bits, host order.
+   */
+  IP6Addr(word_store_type::value_type msw, word_store_type::value_type lsw) : _addr{{msw, lsw}} {}
+
+  friend IP6Addr operator&(IP6Addr const &addr, IPMask const &mask);
+
+  friend IP6Addr operator|(IP6Addr const &addr, IPMask const &mask);
+};
+
+/** An IPv4 or IPv6 address.
+ *
+ * The family type is stored. For comparisons, invalid < IPv4 < IPv6. All invalid instances are equal.
+ */
+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.
+  self_type & operator = (self_type const& that) = default; ///< Copy assignment.
+
+  /// Construct using IPv4 @a addr.
+  explicit IPAddr(in_addr_t addr);
+
+  /// Construct using an IPv4 @a addr
+  IPAddr(IP4Addr const &addr) : _addr{addr}, _family(IP4Addr::AF_value) {}
+
+  /// Construct using IPv6 @a addr.
+  explicit IPAddr(in6_addr const &addr);
+
+  /// construct using an IPv6 @a addr
+  IPAddr(IP6Addr const &addr) : _addr{addr}, _family(IP6Addr::AF_value) {}
+
+  /// 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(string_view const &text);
+
+  /// Set to the address in @a addr.
+  self_type &assign(sockaddr const *addr);
+
+  /// Set to the address in @a addr.
+  self_type &assign(sockaddr_in const *addr);
+
+  /// Set to the address in @a addr.
+  self_type &assign(sockaddr_in6 const *addr);
+
+  /// Set to IPv4 @a addr.
+  self_type &assign(in_addr_t addr);
+
+  /// Set to IPv6 @a addr
+  self_type &assign(in6_addr const &addr);
+
+  /// Assign from end point.
+  self_type &operator=(IPEndpoint const &ip);
+
+  /// Assign from IPv4 raw address.
+  self_type &operator=(in_addr_t ip);
+
+  /// Assign from IPv6 raw address.
+  self_type &operator=(in6_addr const &addr);
+
+  bool operator==(self_type const &that) const;
+
+  bool operator!=(self_type const &that) const;
+
+  bool operator<(self_type const &that) const;
+
+  bool operator>(self_type const &that) const;
+
+  bool operator<=(self_type const &that) const;
+
+  bool operator>=(self_type const &that) const;
+
+  /// Assign from @c sockaddr
+  self_type &operator=(sockaddr const *addr);
+
+  self_type &operator&=(IPMask const &mask);
+
+  self_type &operator|=(IPMask const &mask);
+
+  /** 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;
+
+  /// Test for same address family.
+  /// @c return @c true if @a that is the same address family as @a this.
+  bool is_same_family(self_type const &that);
+
+  /// Get the address family.
+  /// @return The address family.
+  sa_family_t family() const;
+
+  /// Test for IPv4.
+  bool is_ip4() const;
+
+  /// Test for IPv6.
+  bool is_ip6() const;
+
+  /// @return As IPv4 address - results are undefined if it is not actually IPv4.
+  IP4Addr const &ip4() const;
+
+  /// @return As IPv6 address - results are undefined if it is not actually IPv6.
+  IP6Addr const &ip6() const;
+
+  /// Test for validity.
+  bool is_valid() const;
+
+  /// Make invalid.
+  self_type &invalidate();
+
+  /// Test for loopback
+  bool is_loopback() const;
+
+  /// Test for multicast
+  bool is_multicast() const;
+
+  /// @return @c true if this is a link local address, @c false if not.
+  bool is_link_local() const;
+
+  /// @return @c true if this is a private address, @c false if not.
+  bool is_private() const;
+
+  ///< Pre-constructed invalid instance.
+  static self_type const INVALID;
+
+protected:
+  friend IP4Addr;
+  friend IP6Addr;
+
+  /// Address data.
+  union raw_addr_type {
+    IP4Addr _ip4;                                    ///< IPv4 address (host)
+    IP6Addr _ip6;                                    ///< IPv6 address (host)
+
+    constexpr raw_addr_type();
+
+    raw_addr_type(in_addr_t addr) : _ip4(addr) {}
+
+    raw_addr_type(in6_addr const &addr) : _ip6(addr) {}
+
+    raw_addr_type(IP4Addr const &addr) : _ip4(addr) {}
+
+    raw_addr_type(IP6Addr const &addr) : _ip6(addr) {}
+  } _addr;
+
+  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.
+
+  friend class IP4Addr;
+  friend class IP6Addr;
+
+public:
+  using raw_type = uint8_t; ///< Storage for mask width.
+
+  IPMask() = default; ///< Default construct to invalid mask.
+
+  /** Construct a mask of @a width.
+   *
+   * @param width Number of bits in the mask.
+   *
+   * @note Because this is a network mask, it is always left justified.
+   */
+  explicit IPMask(raw_type width);
+
+  /// @return @c true if the mask is valid, @c false if not.
+  bool is_valid() const;
+
+  /** Parse mask from @a text.
+   *
+   * @param text A number in string format.
+   * @return @a true if a valid CIDR value, @c false if not.
+   */
+  bool load(string_view const &text);
+
+  /** Copmute a mask for the network at @a addr.
+   * @param addr Lower bound of network.
+   * @return A mask with the width of the largest network starting at @a addr.
+   */
+  static self_type mask_for(IPAddr const &addr);
+
+  /** Copmute a mask for the network at @a addr.
+   * @param addr Lower bound of network.
+   * @return A mask with the width of the largest network starting at @a addr.
+   */
+  static self_type mask_for(IP4Addr const &addr);
+
+  /** Copmute a mask for the network at @a addr.
+   * @param addr Lower bound of network.
+   * @return A mask with the width of the largest network starting at @a addr.
+   */
+  static self_type mask_for(IP6Addr const &addr);
+
+  /// Change to default constructed state (invalid).
+  self_type & clear();
+
+  /// The width of the mask.
+  raw_type width() const;
+
+  /** Extend the mask (cover more addresses).
+   *
+   * @param n Number of bits to extend.
+   * @return @a this
+   *
+   * Effectively shifts the mask left, bringing in 0 bits on the right.
+   */
+  self_type & operator<<=(raw_type n);
+
+  /** Narrow the mask (cover fewer addresses).
+   *
+   * @param n Number of bits to narrow.
+   * @return @a this
+   *
+   * Effectively shift the mask right, bringing in 1 bits on the left.
+   */
+  self_type & operator>>=(raw_type n);
+
+  /** The mask as an IPv4 address.
+   *
+   * @return An IPv4 address that is the mask.
+   *
+   * If the mask is wider than an IPv4 address, the maximum mask is returned.
+   */
+  IP4Addr as_ip4() const;
+
+  /** The mask as an IPv6 address.
+   *
+   * @return An IPv6 address that is the mask.
+   *
+   * If the mask is wider than an IPv6 address, the maximum mask is returned.
+   */
+  IP6Addr as_ip6() const;
+
+protected:
+  /// Marker value for an invalid mask.
+  static constexpr auto INVALID = std::numeric_limits<raw_type>::max();
+
+  raw_type _cidr = INVALID; ///< Mask width in bits.
+
+  /// Compute a partial IPv6 mask, sized for the basic storage type.
+  static raw_type mask_for_quad(IP6Addr::quad_type q);
+};
+
+// --- Implementation
+
+inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {}
+
+inline IP4Addr::IP4Addr(string_view const &text) {
+  if (!this->load(text)) {
+    _addr = INADDR_ANY;
+  }
+}
+
+inline constexpr sa_family_t
+IP4Addr::family() const {
+  return AF_value;
+}
+
+inline IP4Addr &
+IP4Addr::operator<<=(unsigned n) {
+  _addr <<= n;
+  return *this;
+}
+
+inline IP4Addr &
+IP4Addr::operator>>=(unsigned n) {
+  _addr >>= n;
+  return *this;
+}
+
+inline IP4Addr &
+IP4Addr::operator&=(self_type const &that) {
+  _addr &= that._addr;
+  return *this;
+}
+
+inline IP4Addr &
+IP4Addr::operator|=(self_type const &that) {
+  _addr |= that._addr;
+  return *this;
+}
+
+inline IP4Addr &
+IP4Addr::operator++() {
+  ++_addr;
+  return *this;
+}
+
+inline IP4Addr &
+IP4Addr::operator--() {
+  --_addr;
+  return *this;
+}
+
+inline in_addr_t
+IP4Addr::network_order() const {
+  return htonl(_addr);
+}
+
+inline in_addr_t
+IP4Addr::host_order() const {
+  return _addr;
+}
+
+inline auto
+IP4Addr::operator=(in_addr_t ip) -> self_type & {
+  _addr = ntohl(ip);
+  return *this;
+}
+
+/// Equality.
+inline bool
+operator==(IP4Addr const &lhs, IP4Addr const &rhs) {
+  return lhs._addr == rhs._addr;
+}
+
+/// @return @c true if @a lhs is equal to @a rhs.
+inline bool
+operator!=(IP4Addr const &lhs, IP4Addr const &rhs) {
+  return lhs._addr != rhs._addr;
+}
+
+/// @return @c true if @a lhs is less than @a rhs (host order).
+inline bool
+operator<(IP4Addr const &lhs, IP4Addr const &rhs) {
+  return lhs._addr < rhs._addr;
+}
+
+/// @return @c true if @a lhs is less than or equal to@a rhs (host order).
+inline bool
+operator<=(IP4Addr const &lhs, IP4Addr const &rhs) {
+  return lhs._addr <= rhs._addr;
+}
+
+/// @return @c true if @a lhs is greater than @a rhs (host order).
+inline bool
+operator>(IP4Addr const &lhs, IP4Addr const &rhs) {
+  return rhs < lhs;
+}
+
+/// @return @c true if @a lhs is greater than or equal to @a rhs (host order).
+inline bool
+operator>=(IP4Addr const &lhs, IP4Addr const &rhs) {
+  return rhs <= lhs;
+}
+
+inline IP4Addr &
+IP4Addr::operator&=(IPMask const &mask) {
+  _addr &= mask.as_ip4()._addr;
+  return *this;
+}
+
+inline IP4Addr &
+IP4Addr::operator|=(IPMask const &mask) {
+  _addr |= ~(mask.as_ip4()._addr);
+  return *this;
+}
+
+inline bool
+IP4Addr::is_any() const {
+  return _addr == INADDR_ANY;
+}
+
+inline bool
+IP4Addr::is_loopback() const {
+  return (*this)[0] == IN_LOOPBACKNET;
+}
+
+inline bool
+IP4Addr::is_multicast() const {
+  return IN_MULTICAST(_addr);
+}
+
+inline bool
+IP4Addr::is_link_local() const {
+  return (_addr & 0xFFFF0000) == 0xA9FE0000; // 169.254.0.0/16
+}
+
+inline bool IP4Addr::is_private() const {
+  return (((_addr & 0xFF000000) == 0x0A000000) ||        // 10.0.0.0/8
+          ((_addr & 0xFFC00000) == 0x64400000) ||        // 100.64.0.0/10
+          ((_addr & 0xFFF00000) == 0xAC100000) || // 172.16.0.0/12
+          ((_addr & 0xFFFF0000) == 0xC0A80000)           // 192.168.0.0/16
+  );
+}
+
+inline uint8_t
+IP4Addr::operator[](unsigned int idx) const {
+  return reinterpret_cast<bytes const &>(_addr)[3 - idx];
+}
+
+inline int
+IP4Addr::cmp(IP4Addr::self_type const &that) const {
+  return _addr < that._addr ? -1 : _addr > that._addr ? 1 : 0;
+}
+
+constexpr in_addr_t
+IP4Addr::reorder(in_addr_t src) {
+  return ((src & 0xFF) << 24) | (((src >> 8) & 0xFF) << 16) | (((src >> 16) & 0xFF) << 8) | ((src >> 24) & 0xFF);
+}
+
+// +++ IP6Addr +++
+
+inline constexpr sa_family_t
+IP6Addr::family() const {
+  return AF_value;
+}
+
+inline IP6Addr::IP6Addr(in6_addr const &addr) {
+  *this = addr;
+}
+
+inline IP6Addr::IP6Addr(string_view const &text) {
+  if (!this->load(text)) {
+    this->clear();
+  }
+}
+
+inline IP6Addr::IP6Addr(IP4Addr addr) {
+  _addr._store[MSW] = 0;
+  _addr._quad[QUAD_IDX[4]] = 0;
+  _addr._quad[QUAD_IDX[5]] = 0xffff;
+  _addr._quad[QUAD_IDX[6]] = addr.host_order() >> QUAD_WIDTH;
+  _addr._quad[QUAD_IDX[7]] = addr.host_order();
+}
+
+inline bool
+IP6Addr::is_loopback() const {
+  return _addr._store[MSW] == 0 && _addr._store[LSW] == 1;
+}
+
+inline bool
+IP6Addr::is_multicast() const {
+  return _addr._raw[RAW_IDX[0]] == 0xFF;
+}
+
+inline bool
+IP6Addr::is_any() const {
+  return _addr._store[MSW] == 0 && _addr._store[LSW] == 0;
+}
+
+inline bool
+IP6Addr::is_mapped_ip4() const {
+  return 0 == _addr._store[MSW] && (_addr._quad[QUAD_IDX[4]] == 0 && _addr._quad[QUAD_IDX[5]] == 0xFFFF);
+}
+
+inline bool
+IP6Addr::is_link_local() const {
+  return _addr._raw[RAW_IDX[0]] == 0xFE && (_addr._raw[RAW_IDX[1]] & 0xC0) == 0x80; // fe80::/10
+}
+
+inline bool IP6Addr::is_private() const {
+  return (_addr._raw[RAW_IDX[0]]& 0xFE) == 0xFC; // fc00::/7
+}
+
+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::clear() -> self_type & {
+  _addr._store[MSW] = _addr._store[LSW] = 0;
+  return *this;
+}
+
+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) {
+    *this = addr->sin6_addr;
+  } else {
+    this->clear();
+  }
+  return *this;
+}
+
+inline IP6Addr &
+IP6Addr::operator++() {
+  if (++(_addr._store[LSW]) == 0) {
+    ++(_addr._store[MSW]);
+  }
+  return *this;
+}
+
+inline IP6Addr &
+IP6Addr::operator--() {
+  if (--(_addr._store[LSW]) == ~static_cast<uint64_t>(0)) {
+    --(_addr._store[MSW]);
+  }
+  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)];
+  }
+}
+
+/// @return @c true if @a lhs is equal to @a rhs.
+inline bool
+operator==(IP6Addr const &lhs, IP6Addr const &rhs) {
+  return lhs._addr._store[IP6Addr::MSW] == rhs._addr._store[IP6Addr::MSW] && lhs._addr._store[IP6Addr::LSW] == rhs._addr._store[IP6Addr::LSW];
+}
+
+/// @return @c true if @a lhs is not equal to @a rhs.
+inline bool
+operator!=(IP6Addr const &lhs, IP6Addr const &rhs) {
+  return lhs._addr._store[IP6Addr::MSW] != rhs._addr._store[IP6Addr::MSW] || lhs._addr._store[IP6Addr::LSW] != rhs._addr._store[IP6Addr::LSW];
+}
+
+/// @return @c true if @a lhs is less than @a rhs.
+inline bool
+operator<(IP6Addr const &lhs, IP6Addr const &rhs) {
+  return lhs._addr._store[IP6Addr::MSW] < rhs._addr._store[IP6Addr::MSW] ||
+  (lhs._addr._store[IP6Addr::MSW] == rhs._addr._store[IP6Addr::MSW] && lhs._addr._store[IP6Addr::LSW] < rhs._addr._store[IP6Addr::LSW]);
+}
+
+/// @return @c true if @a lhs is greater than @a rhs.
+inline bool
+operator>(IP6Addr const &lhs, IP6Addr const &rhs) {
+  return rhs < lhs;
+}
+
+/// @return @c true if @a lhs is less than or equal to @a rhs.
+inline bool
+operator<=(IP6Addr const &lhs, IP6Addr const &rhs) {
+  return lhs._addr._store[IP6Addr::MSW] < rhs._addr._store[IP6Addr::MSW] ||
+  (lhs._addr._store[IP6Addr::MSW] == rhs._addr._store[IP6Addr::MSW] && lhs._addr._store[IP6Addr::LSW] <= rhs._addr._store[IP6Addr::LSW]);
+}
+
+/// @return @c true if @a lhs is greater than or equal to @a rhs.
+inline bool
+operator>=(IP6Addr const &lhs, IP6Addr const &rhs) {
+  return rhs <= lhs;
+}
+
+inline IP6Addr &
+IP6Addr::operator&=(IPMask const &mask) {
+  if (mask._cidr < WORD_WIDTH) {
+    _addr._store[MSW] &= (~word_type{0} << (WORD_WIDTH - mask._cidr));
+    _addr._store[LSW] = 0;
+  } else if (mask._cidr < WIDTH) {
+    _addr._store[LSW] &= (~word_type{0} << (2 * WORD_WIDTH - mask._cidr));
+  }
+  return *this;
+}
+
+inline IP6Addr &
+IP6Addr::operator|=(IPMask const &mask) {
+  if (mask._cidr < WORD_WIDTH) {
+    _addr._store[MSW] |= (~word_type{0} >> mask._cidr);
+    _addr._store[LSW] = ~word_type{0};
+  } else if (mask._cidr < WIDTH) {
+    _addr._store[LSW] |= (~word_type{0} >> (mask._cidr - WORD_WIDTH));
+  }
+  return *this;
+}
+
+// +++ IPMask +++
+
+inline IPMask::IPMask(raw_type width) : _cidr(width) {}
+
+inline bool
+IPMask::is_valid() const {
+  return _cidr < INVALID;
+}
+
+inline auto
+IPMask::width() const -> raw_type {
+  return _cidr;
+}
+
+inline bool
+operator==(IPMask const &lhs, IPMask const &rhs) {
+  return lhs.width() == rhs.width();
+}
+
+inline bool
+operator!=(IPMask const &lhs, IPMask const &rhs) {
+  return lhs.width() != rhs.width();
+}
+
+inline bool
+operator<(IPMask const &lhs, IPMask const &rhs) {
+  return lhs.width() < rhs.width();
+}
+
+inline IP4Addr
+IPMask::as_ip4() const {
+  static constexpr auto MASK = ~in_addr_t{0};
+  in_addr_t addr             = MASK;
+  if (_cidr < IP4Addr::WIDTH) {
+    addr <<= IP4Addr::WIDTH - _cidr;
+  }
+  return IP4Addr{addr};
+}
+
+inline auto
+IPMask::clear() ->  self_type & {
+  _cidr = INVALID;
+  return *this;
+}
+
+inline auto
+IPMask::operator<<=(raw_type n) -> self_type & {
+  _cidr -= n;
+  return *this;
+}
+
+inline auto
+IPMask::operator>>=(IPMask::raw_type n) -> self_type & {
+  _cidr += n;
+  return *this;
+}
+
+// +++ mixed mask operators +++
+
+inline IP4Addr
+operator&(IP4Addr const &addr, IPMask const &mask) {
+  return IP4Addr{addr} &= mask;
+}
+
+inline IP4Addr
+operator|(IP4Addr const &addr, IPMask const &mask) {
+  return IP4Addr{addr} |= mask;
+}
+
+inline IP6Addr
+operator&(IP6Addr const &addr, IPMask const &mask) {
+  return IP6Addr{addr} &= mask;
+}
+
+inline IP6Addr
+operator|(IP6Addr const &addr, IPMask const &mask) {
+  return IP6Addr{addr} |= mask;
+}
+
+constexpr uint8_t
+IP6Addr::operator[](int idx) const {
+  return _addr._raw[RAW_IDX[idx]];
+}
+
+inline IPAddr
+operator&(IPAddr const &addr, IPMask const &mask) {
+  return IPAddr{addr} &= mask;
+}
+
+inline IPAddr
+operator|(IPAddr const &addr, IPMask const &mask) {
+  return IPAddr{addr} |= mask;
+}
+
+// @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 IPAddr::IPAddr(in_addr_t addr) : _addr(addr), _family(IP4Addr::AF_value) {}
+
+inline IPAddr::IPAddr(in6_addr const &addr) : _addr(addr), _family(IP6Addr::AF_value) {}
+
+inline IPAddr::IPAddr(sockaddr const *addr) {
+  this->assign(addr);
+}
+
+inline IPAddr::IPAddr(string_view const &text) {
+  this->load(text);
+}
+
+inline IPAddr &
+IPAddr::operator=(in_addr_t addr) {
+  _family    = AF_INET;
+  _addr._ip4 = addr;
+  return *this;
+}
+
+inline IPAddr &
+IPAddr::operator=(in6_addr const &addr) {
+  _family    = AF_INET6;
+  _addr._ip6 = addr;
+  return *this;
+}
+
+inline IPAddr &
+IPAddr::operator=(sockaddr const *addr) {
+  return this->assign(addr);
+}
+
+inline sa_family_t
+IPAddr::family() const {
+  return _family;
+}
+
+inline bool
+IPAddr::is_ip4() const {
+  return AF_INET == _family;
+}
+
+inline bool
+IPAddr::is_ip6() const {
+  return AF_INET6 == _family;
+}
+
+inline bool
+IPAddr::is_same_family(self_type const &that) {
+  return this->is_valid() && _family == that._family;
+}
+
+inline bool
+IPAddr::is_loopback() const {
+  return (AF_INET == _family && _addr._ip4.is_loopback()) || (AF_INET6 == _family && _addr._ip6.is_loopback());
+}
+
+inline bool
+IPAddr::is_link_local() const {
+  return this->is_ip4() ? this->ip4().is_link_local()
+         : this->is_ip6() ? this->ip6().is_link_local()
+                          : false;
+}
+
+inline bool
+IPAddr::is_private() const {
+  return this->is_ip4() ? this->ip4().is_private()
+         : this->is_ip6() ? this->ip6().is_private()
+                          : false;
+}
+
+inline IPAddr &
+IPAddr::assign(in_addr_t addr) {
+  _family    = AF_INET;
+  _addr._ip4 = addr;
+  return *this;
+}
+
+inline IPAddr &
+IPAddr::assign(in6_addr const &addr) {
+  _family    = AF_INET6;
+  _addr._ip6 = addr;
+  return *this;
+}
+
+inline IPAddr &
+IPAddr::assign(sockaddr_in const *addr) {
+  if (addr) {
+    _family    = AF_INET;
+    _addr._ip4 = addr;
+  } else {
+    _family = AF_UNSPEC;
+  }
+  return *this;
+}
+
+inline IPAddr &
+IPAddr::assign(sockaddr_in6 const *addr) {
+  if (addr) {
+    _family    = AF_INET6;
+    _addr._ip6 = addr->sin6_addr;
+  } else {
+    _family = AF_UNSPEC;
+  }
+  return *this;
+}
+
+inline bool
+IPAddr::is_valid() const {
+  return _family == AF_INET || _family == AF_INET6;
+}
+
+inline IPAddr &
+IPAddr::invalidate() {
+  _family = AF_UNSPEC;
+  return *this;
+}
+
+// Associated operators.
+
+/// Equality.
+bool operator==(IPAddr const &lhs, sockaddr const *rhs);
+
+/// Equality.
+inline bool
+operator==(sockaddr const *lhs, IPAddr const &rhs) {
+  return rhs == lhs;
+}
+
+/// Inequality.
+inline bool
+operator!=(IPAddr const &lhs, sockaddr const *rhs) {
+  return !(lhs == rhs);
+}
+
+/// Inequality.
+inline bool
+operator!=(sockaddr const *lhs, IPAddr const &rhs) {
+  return !(rhs == lhs);
+}
+
+/// Equality.
+inline IP4Addr const &
+IPAddr::ip4() const {
+  return _addr._ip4;
+}
+
+inline IP6Addr const &
+IPAddr::ip6() const {
+  return _addr._ip6;
+}
+
+inline bool
+IPAddr::operator==(self_type const &that) const {
+  switch (_family) {
+  case AF_INET:
+    return that._family == AF_INET && _addr._ip4 == that._addr._ip4;
+    case AF_INET6:
+      return that._family == AF_INET6 && _addr._ip6 == that._addr._ip6;
+      default:
+        return ! that.is_valid();
+  }
+}
+
+inline bool
+IPAddr::operator!=(self_type const &that) const {
+  return !(*this == that);
+}
+
+inline bool
+IPAddr::operator>(self_type const &that) const {
+  return that < *this;
+}
+
+inline bool
+IPAddr::operator<=(self_type const &that) const {
+  return !(that < *this);
+}
+
+inline bool
+IPAddr::operator>=(self_type const &that) const {
+  return !(*this < that);
+}
+
+// Disambiguating between comparisons and implicit conversions.
+
+inline bool
+operator==(IPAddr const &lhs, IP4Addr const &rhs) {
+  return lhs.is_ip4() && lhs.ip4() == rhs;
+}
+
+inline bool
+operator!=(IPAddr const &lhs, IP4Addr const &rhs) {
+  return !lhs.is_ip4() || lhs.ip4() != rhs;
+}
+
+inline bool
+operator==(IP4Addr const &lhs, IPAddr const &rhs) {
+  return rhs.is_ip4() && lhs == rhs.ip4();
+}
+
+inline bool
+operator!=(IP4Addr const &lhs, IPAddr const &rhs) {
+  return !rhs.is_ip4() || lhs != rhs.ip4();
+}
+
+inline bool
+operator==(IPAddr const &lhs, IP6Addr const &rhs) {
+  return lhs.is_ip6() && lhs.ip6() == rhs;
+}
+
+inline bool
+operator!=(IPAddr const &lhs, IP6Addr const &rhs) {
+  return !lhs.is_ip6() || lhs.ip6() != rhs;
+}
+
+inline bool
+operator==(IP6Addr const &lhs, IPAddr const &rhs) {
+  return rhs.is_ip6() && lhs == rhs.ip6();
+}
+
+inline bool
+operator!=(IP6Addr const &lhs, IPAddr const &rhs) {
+  return !rhs.is_ip6() || lhs != rhs.ip6();
+}
+}} // namespace swoc::SWOC_VERSION_NS
+
+namespace std {
+
+/// Standard hash support for @a IP4Addr.
+template <> struct hash<swoc::IP4Addr> {
+  size_t operator()(swoc::IP4Addr const &addr) const {
+    return addr.network_order();
+  }
+};
+
+/// Standard hash support for @a IP6Addr.
+template <> struct hash<swoc::IP6Addr> {
+  size_t operator()(swoc::IP6Addr const &addr) const {
+    // XOR the 64 chunks then XOR that down to 32 bits.
+    auto words = addr.as_span<uint64_t>();
+    return words[0] ^ words[1];
+  }
+};
+
+/// Standard hash support for @a IPAddr.
+template <> struct hash<swoc::IPAddr> {
+  size_t operator()(swoc::IPAddr const &addr) const {
+    return addr.is_ip4() ? hash<swoc::IP4Addr>()(addr.ip4()) : addr.is_ip6() ? hash<swoc::IP6Addr>()(addr.ip6()) : 0;
+  }
+};
+
+} // namespace std
diff --git a/lib/swoc/include/swoc/IPEndpoint.h b/lib/swoc/include/swoc/IPEndpoint.h
new file mode 100644
index 0000000..4ae875c
--- /dev/null
+++ b/lib/swoc/include/swoc/IPEndpoint.h
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Network Geographics 2014
+/** @file
+   IPEndpoint - wrapper for raw socket address objects.
+ */
+
+#pragma once
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <stdexcept>
+
+#include "swoc/swoc_version.h"
+#include "swoc/TextView.h"
+
+namespace swoc { inline namespace SWOC_VERSION_NS {
+
+using ::std::string_view;
+
+class IPAddr;
+class IP4Addr;
+class IP6Addr;
+
+namespace detail {
+extern void *const pseudo_nullptr;
+}
+
+/** 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
+    instances. These are all duplicates of methods that operate on the internal @c sockaddr and
+    are provided primarily for backwards compatibility during the shift to using this class.
+
+    We use the term "endpoint" because these contain more than just the raw address, all of the data
+    for an IP endpoint is present.
+ */
+union IPEndpoint {
+  using self_type = IPEndpoint; ///< Self reference type.
+
+  struct sockaddr sa;      ///< Generic address.
+  struct sockaddr_in sa4;  ///< IPv4
+  struct sockaddr_in6 sa6; ///< IPv6
+
+  /// Default construct invalid instance.
+  IPEndpoint();
+  IPEndpoint(self_type const &that); ///< Copy constructor.
+  ~IPEndpoint() = default;
+
+  /// Construct from the @a text representation of an address.
+  explicit IPEndpoint(string_view const &text);
+
+  // Construct from @c IPAddr
+  explicit IPEndpoint(IPAddr const &addr);
+
+  // Construct from @c sockaddr
+  IPEndpoint(sockaddr const *sa);
+
+  /// Copy assignment.
+  self_type &operator=(self_type const &that);
+
+  /** Break a string in to IP address relevant tokens.
+   *
+   * @param src Source text. [in]
+   * @param host The host / address. [out]
+   * @param port The host_order_port. [out]
+   * @param rest Any text past the end of the IP address. [out]
+   * @return @c true if an IP address was found, @c false otherwise.
+   *
+   * Any of the out parameters can be @c nullptr in which case they are not updated.
+   * This parses and discards the IPv6 brackets.
+   *
+   * @note This is intended for internal use to do address parsing, but it can be useful in other contexts.
+   */
+  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.
+
+      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(string_view const &str);
+
+  /// Invalidate a @c sockaddr.
+  static void invalidate(sockaddr *addr);
+
+  /// Invalidate this endpoint.
+  self_type &invalidate();
+
+  /** Copy (assign) the contents of @a src to @a dst.
+   *
+   * The caller must ensure @a dst is large enough to hold the contents of @a src, the size of which
+   * can vary depending on the type of address in @a dst.
+   *
+   * @param dst Destination.
+   * @param src Source.
+   * @return @c true if @a dst is a valid IP address, @c false otherwise.
+   */
+  static bool assign(sockaddr *dst, sockaddr const *src);
+
+  /** Assign from a socket address.
+      The entire address (all parts) are copied if the @a ip is valid.
+  */
+  self_type &assign(sockaddr const *addr);
+
+  /// Assign from an @a addr and @a host_order_port.
+  self_type &assign(IPAddr const &addr, in_port_t port = 0);
+
+  /// Copy to @a sa.
+  const self_type &copy_to(sockaddr *addr) const;
+
+  /// Test for valid IP address.
+  bool is_valid() const;
+
+  /// Test for IPv4.
+  bool is_ip4() const;
+
+  /// Test for IPv6.
+  bool is_ip6() const;
+
+  /** Effectively size of the address.
+   *
+   * @return The size of the structure appropriate for the address family of the stored address.
+   */
+  socklen_t size() const;
+
+  /// @return The IP address family.
+  sa_family_t family() const;
+
+  /// Set to be the ANY address for family @a family.
+  /// @a family must be @c AF_INET or @c AF_INET6.
+  /// @return This object.
+  self_type &set_to_any(int family);
+
+  /// @return @c true if this is the ANY address, @c false if not.
+  bool is_any() const;
+
+  /// Set to be loopback address for family @a family.
+  /// @a family must be @c AF_INET or @c AF_INET6.
+  /// @return This object.
+  self_type &set_to_loopback(int family);
+
+  /// @return @c true if this is a loopback address, @c false if not.
+  bool is_loopback() const;
+
+  /// Port in network order.
+  in_port_t &network_order_port();
+
+  /// Port in network order.
+  in_port_t network_order_port() const;
+
+  /// Port in host horder.
+  in_port_t host_order_port() const;
+
+  /// Port in network order from @a sockaddr.
+  static in_port_t &port(sockaddr *sa);
+
+  /// Port in network order from @a sockaddr.
+  static in_port_t port(sockaddr const *sa);
+
+  /// Port in host order directly from a @c sockaddr
+  static in_port_t host_order_port(sockaddr const *sa);
+
+  /// Automatic conversion to @c sockaddr.
+  operator sockaddr *() { return &sa; }
+
+  /// Automatic conversion to @c sockaddr.
+  operator sockaddr const *() const { return &sa; }
+
+  /// The string name of the address family.
+  static string_view family_name(sa_family_t family);
+};
+
+inline IPEndpoint::IPEndpoint() {
+  sa.sa_family = AF_UNSPEC;
+}
+
+inline IPEndpoint::IPEndpoint(IPAddr const &addr) {
+  this->assign(addr);
+}
+
+inline IPEndpoint::IPEndpoint(sockaddr const *sa) {
+  this->assign(sa);
+}
+
+inline IPEndpoint::IPEndpoint(IPEndpoint::self_type const &that) : self_type(&that.sa) {}
+
+inline IPEndpoint &
+IPEndpoint::invalidate() {
+  sa.sa_family = AF_UNSPEC;
+  return *this;
+}
+
+inline void
+IPEndpoint::invalidate(sockaddr *addr) {
+  addr->sa_family = AF_UNSPEC;
+}
+
+inline bool
+IPEndpoint::is_valid() const {
+  return sa.sa_family == AF_INET || sa.sa_family == AF_INET6;
+}
+
+inline IPEndpoint &
+IPEndpoint::operator=(self_type const &that) {
+  self_type::assign(&sa, &that.sa);
+  return *this;
+}
+
+inline IPEndpoint &
+IPEndpoint::assign(sockaddr const *src) {
+  self_type::assign(&sa, src);
+  return *this;
+}
+
+inline IPEndpoint const &
+IPEndpoint::copy_to(sockaddr *addr) const {
+  self_type::assign(addr, &sa);
+  return *this;
+}
+
+inline bool
+IPEndpoint::is_ip4() const {
+  return AF_INET == sa.sa_family;
+}
+
+inline bool
+IPEndpoint::is_ip6() const {
+  return AF_INET6 == sa.sa_family;
+}
+
+inline sa_family_t
+IPEndpoint::family() const {
+  return sa.sa_family;
+}
+
+inline in_port_t &
+IPEndpoint::network_order_port() {
+  return self_type::port(&sa);
+}
+
+inline in_port_t
+IPEndpoint::network_order_port() const {
+  return self_type::port(&sa);
+}
+
+inline in_port_t
+IPEndpoint::host_order_port() const {
+  return ntohs(this->network_order_port());
+}
+
+inline in_port_t &
+IPEndpoint::port(sockaddr *sa) {
+  switch (sa->sa_family) {
+  case AF_INET:
+    return reinterpret_cast<sockaddr_in *>(sa)->sin_port;
+    case AF_INET6:
+      return reinterpret_cast<sockaddr_in6 *>(sa)->sin6_port;
+  }
+  // Force a failure upstream by returning a null reference.
+  throw std::domain_error("sockaddr is not a valid IP address");
+}
+
+inline in_port_t
+IPEndpoint::port(sockaddr const *sa) {
+  return self_type::port(const_cast<sockaddr *>(sa));
+}
+
+inline in_port_t
+IPEndpoint::host_order_port(sockaddr const *sa) {
+  return ntohs(self_type::port(sa));
+}
+
+}} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/IPRange.h b/lib/swoc/include/swoc/IPRange.h
new file mode 100644
index 0000000..b367807
--- /dev/null
+++ b/lib/swoc/include/swoc/IPRange.h
@@ -0,0 +1,1850 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Network Geographics 2014
+/** @file
+   IP address range utilities.
+ */
+
+#pragma once
+
+#include <swoc/DiscreteRange.h>
+#include <swoc/IPAddr.h>
+
+namespace swoc { inline namespace SWOC_VERSION_NS {
+
+using ::std::string_view;
+
+class IP4Net;
+class IP6Net;
+class IPNet;
+
+namespace detail {
+extern void *const pseudo_nullptr;
+}
+
+/** 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:
+  /// Default constructor, invalid range.
+  IP4Range() = default;
+
+  /// Construct from an network expressed as @a addr and @a mask.
+  IP4Range(IP4Addr const &addr, IPMask const &mask);
+
+  /// Construct from super type.
+  /// @internal Why do I have to do this, even though the super type constructors are inherited?
+  IP4Range(super_type const &r) : super_type(r) {}
+
+  /** Construct range from @a text.
+   *
+   * @param text Range text.
+   * @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 const &text);
+
+  using super_type::super_type; ///< Import super class constructors.
+
+  /** Set @a this range.
+   *
+   * @param addr Minimum address.
+   * @param mask CIDR mask to compute maximum adddress from @a addr.
+   * @return @a this
+   */
+  self_type &assign(IP4Addr const &addr, IPMask const &mask);
+
+  using super_type::assign; ///< Import assign methods.
+
+  /** Assign to this range from text.
+   *
+   * @param text Range text.
+   *
+   * The text must be in one of three formats.
+   * - A dashed range, "addr1-addr2"
+   * - A singleton, "addr". This is treated as if it were "addr-addr", a range of size 1.
+   * - CIDR notation, "addr/cidr" where "cidr" is a number from 0 to the number of bits in the address.
+   */
+  bool load(string_view text);
+
+  /** Compute the mask for @a this as a network.
+   *
+   * @return If @a this is a network, the mask for that network. Otherwise an invalid mask.
+   *
+   * @see IPMask::is_valid
+   */
+  IPMask network_mask() const;
+
+  class NetSource;
+
+  /** Generate a list of networks covering @a this range.
+   *
+   * @return A network generator.
+   *
+   * The returned object can be used as an iterator, or as a container to iterating over
+   * the unique minimal set of networks that cover @a this range.
+   *
+   * @code
+   * void (IP4Range const& range) {
+   *   for ( auto const& net : range ) {
+   *     net.addr(); // network address.
+   *     net.mask(); // network mask;
+   *   }
+   * }
+   * @endcode
+   */
+  NetSource networks() const;
+};
+
+/** Network generator class.
+ * This generates networks from a range and acts as both a forward iterator and a container.
+ */
+class IP4Range::NetSource {
+  using self_type = NetSource; ///< Self reference type.
+public:
+  using range_type = IP4Range; ///< Import base range type.
+
+  /// Construct from @a range.
+  explicit NetSource(range_type const &range);
+
+  /// Copy constructor.
+  NetSource(self_type const &that) = default;
+
+  /// This class acts as a container and an iterator.
+  using iterator = self_type;
+  /// All iteration is constant so no distinction between iterators.
+  using const_iterator = iterator;
+
+  iterator begin() const; ///< First network.
+  static iterator end() ;   ///< Past last network.
+
+  /// Return @c true if there are valid networks, @c false if not.
+  bool empty() const;
+
+  /// @return The current network.
+  IP4Net operator*() const;
+
+  /// Access @a this as if it were an @c IP4Net.
+  self_type *operator->();
+
+  /// Iterator support.
+  /// @return The current network address.
+  IP4Addr const &addr() const;
+
+  /// Iterator support.
+  /// @return The current network mask.
+  IPMask mask() const;
+
+  /// Move to next network.
+  self_type &operator++();
+
+  /// Move to next network.
+  self_type operator++(int);
+
+  /// Equality.
+  bool operator==(self_type const &that) const;
+
+  /// Inequality.
+  bool operator!=(self_type const &that) const;
+
+protected:
+  IP4Range _range; ///< Remaining range.
+  /// Mask for current network.
+  IP4Addr _mask{~static_cast<in_addr_t>(0)};
+  IPMask::raw_type _cidr = IP4Addr::WIDTH; ///< Current CIDR value.
+
+  void search_wider();
+
+  void search_narrower();
+
+  bool is_valid(IP4Addr mask) const;
+};
+
+/// Inclusive range of IPv6 addresses.
+class IP6Range : public DiscreteRange<IP6Addr> {
+  using self_type  = IP6Range;
+  using super_type = DiscreteRange<IP6Addr>;
+
+public:
+  /// Construct from super type.
+  /// @internal Why do I have to do this, even though the super type constructors are inherited?
+  IP6Range(super_type const &r) : super_type(r) {}
+
+  /** 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.
+   */
+  IP6Range(string_view const &text);
+
+  using super_type::super_type; ///< Import super class constructors.
+
+  /** Set @a this range.
+   *
+   * @param addr Minimum address.
+   * @param mask CIDR mask to compute maximum adddress from @a addr.
+   * @return @a this
+   */
+  self_type &assign(IP6Addr const &addr, IPMask const &mask);
+
+  using super_type::assign; ///< Import assign methods.
+
+  /** Assign to this range from text.
+   *
+   * @param text Range text.
+   *
+   * The text must be in one of three formats.
+   * - A dashed range, "addr1-addr2"
+   * - A singleton, "addr". This is treated as if it were "addr-addr", a range of size 1.
+   * - CIDR notation, "addr/cidr" where "cidr" is a number from 0 to the number of bits in the address.
+   */
+  bool load(string_view text);
+
+  /** Compute the mask for @a this as a network.
+   *
+   * @return If @a this is a network, the mask for that network. Otherwise an invalid mask.
+   *
+   * @see IPMask::is_valid
+   */
+  IPMask network_mask() const;
+
+  class NetSource;
+
+  /** Generate a list of networks covering @a this range.
+   *
+   * @return A network generator.
+   *
+   * The returned object can be used as an iterator, or as a container to iterating over
+   * the unique minimal set of networks that cover @a this range.
+   *
+   * @code
+   * void (IP6Range const& range) {
+   *   for ( auto const& net : range ) {
+   *     net.addr(); // network address.
+   *     net.mask(); // network mask;
+   *   }
+   * }
+   * @endcode
+   */
+  NetSource networks() const;
+};
+
+/** Network generator class.
+ * This generates networks from a range and acts as both a forward iterator and a container.
+ */
+class IP6Range::NetSource {
+  using self_type = NetSource; ///< Self reference type.
+public:
+  using range_type = IP6Range; ///< Import base range type.
+
+  /// Construct from @a range.
+  explicit NetSource(range_type const &range);
+
+  /// Copy constructor.
+  NetSource(self_type const &that) = default;
+
+  /// This class acts as a container and an iterator.
+  using iterator = self_type;
+  /// All iteration is constant so no distinction between iterators.
+  using const_iterator = iterator;
+
+  iterator begin() const; ///< First network.
+  iterator end() const;   ///< Past last network.
+
+  /// @return @c true if there are valid networks, @c false if not.
+  bool empty() const;
+
+  /// @return The current network.
+  IP6Net operator*() const;
+
+  /// Access @a this as if it were an @c IP6Net.
+  self_type *operator->();
+
+  /// @return The current network address.
+  IP6Addr const & addr() const;
+
+  /// Iterator support.
+  /// @return The current network mask.
+  IPMask mask() const;
+
+  /// Move to next network.
+  self_type &operator++();
+
+  /// Move to next network.
+  self_type operator++(int);
+
+  /// Equality.
+  bool operator==(self_type const &that) const;
+
+  /// Inequality.
+  bool operator!=(self_type const &that) const;
+
+protected:
+  IP6Range _range;              ///< Remaining range.
+  IPMask _mask{IP6Addr::WIDTH}; ///< Current CIDR value.
+
+  void search_wider();
+
+  void search_narrower();
+
+  bool is_valid(IPMask const &mask);
+};
+
+/** Range of IP addresses.
+ * Although this can hold IPv4 or IPv6, any specific instance is one or the other, this can never contain
+ * a range of different address families.
+ */
+class IPRange {
+  using self_type = IPRange;
+
+public:
+  /// Default constructor - construct invalid range.
+  IPRange() = default;
+
+  IPRange(IPAddr const &min, IPAddr const &max);
+
+  /// Construct from an IPv4 @a range.
+  IPRange(IP4Range const &range);
+
+  /// Construct from an IPv6 @a range.
+  IPRange(IP6Range const &range);
+
+  /** Construct from a string format.
+   *
+   * @param text Text form of range.
+   *
+   * The string can be a single address, two addresses separated by a dash '-' or a CIDR network.
+   */
+  IPRange(string_view const &text);
+
+  /// Equality
+  bool
+  operator==(self_type const &that) const;
+
+  /// @return @c true if this is an IPv4 range, @c false if not.
+  bool
+  is_ip4() const {
+    return AF_INET == _family;
+  }
+
+  /// @return @c true if this is an IPv6 range, @c false if not.
+  bool
+  is_ip6() const {
+    return AF_INET6 == _family;
+  }
+
+  /** Check if @a this range is the IP address @a family.
+   *
+   * @param family IP address family.
+   * @return @c true if this is @a family, @c false if not.
+   */
+  bool is(sa_family_t family) const;
+
+  /** Load the range from @a text.
+   *
+   * @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);
+
+  /// @return The minimum address in the range.
+  IPAddr min() const;
+
+  /// @return The maximum address in the range.
+  IPAddr max() const;
+
+  /// @return @c true if there are no addresses in the range.
+  bool empty() const;
+
+  /// @return The IPv4 range.
+  IP4Range const & ip4() const { return _range._ip4; }
+
+  /// @return The IPv6 range.
+  IP6Range const & ip6() const { return _range._ip6; }
+
+  /// @return The range family.
+  sa_family_t family() const { return _family; }
+
+  /** Compute the mask for @a this as a network.
+   *
+   * @return If @a this is a network, the mask for that network. Otherwise an invalid mask.
+   *
+   * @see IPMask::is_valid
+   */
+  IPMask network_mask() const;
+
+  class NetSource;
+
+  /** Generate a list of networks covering @a this range.
+   *
+   * @return A network generator.
+   *
+   * The returned object can be used as an iterator, or as a container to iterating over
+   * the unique minimal set of networks that cover @a this range.
+   *
+   * @code
+   * void (IPRange const& range) {
+   *   for ( auto const& net : range ) {
+   *     net.addr(); // network address.
+   *     net.mask(); // network mask;
+   *   }
+   * }
+   * @endcode
+   */
+  NetSource networks() const;
+
+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};
+};
+
+/** Network generator class.
+ * This generates networks from a range and acts as both a forward iterator and a container.
+ */
+class IPRange::NetSource {
+  using self_type = NetSource; ///< Self reference type.
+public:
+  using range_type = IPRange; ///< Import base range type.
+
+  /// Construct from @a range.
+  explicit NetSource(range_type const &range);
+
+  /// Copy constructor.
+  NetSource(self_type const &that) = default;
+
+  /// This class acts as a container and an iterator.
+  using iterator = self_type;
+  /// All iteration is constant so no distinction between iterators.
+  using const_iterator = iterator;
+
+  iterator begin() const; ///< First network.
+  iterator end() const;   ///< Past last network.
+
+  /// @return The current network.
+  IPNet operator*() const;
+
+  /// Access @a this as if it were an @c IP6Net.
+  self_type *operator->();
+
+  /// Iterator support.
+  /// @return The current network address.
+  IPAddr addr() const;
+
+  /// Iterator support.
+  /// @return The current network mask.
+  IPMask mask() const;
+
+  /// Move to next network.
+  self_type &operator++();
+
+  /// Move to next network.
+  self_type operator++(int);
+
+  /// Equality.
+  bool operator==(self_type const &that) const;
+
+  /// Inequality.
+  bool operator!=(self_type const &that) const;
+
+protected:
+  union {
+    std::monostate _nil; ///< Default value, no addresses.
+    IP4Range::NetSource _ip4; ///< IPv4 addresses.
+    IP6Range::NetSource _ip6; ///< IPv6 addresses.
+  };
+  sa_family_t _family = AF_UNSPEC; ///< Mark for union content.
+};
+
+/// An IPv4 network.
+class IP4Net {
+  using self_type = IP4Net; ///< Self reference type.
+public:
+  IP4Net()                      = default; ///< Construct invalid network.
+  IP4Net(self_type const &that) = default; ///< Copy constructor.
+
+  /** Construct from @a addr and @a mask.
+   *
+   * @param addr An address in the network.
+   * @param mask The mask for the network.
+   *
+   * The network is based on the mask, and the resulting network address is chosen such that the
+   * network will contain @a addr. For a given @a addr and @a mask there is only one network
+   * that satisifies these criteria.
+   */
+  IP4Net(IP4Addr addr, IPMask mask);
+
+  IP4Net(swoc::TextView text) { this->load(text); }
+
+  /** Parse network as @a text.
+   *
+   * @param text String describing the network in CIDR format.
+   * @return @c true if a valid string, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// @return @c true if the network is valid, @c false if not.
+  bool is_valid() const;
+
+  /// @return THh smallest address in the network.
+  IP4Addr lower_bound() const;
+
+  /// @return The largest address in the network.
+  IP4Addr upper_bound() const;
+
+  /// @return The mask for the network.
+  IPMask const &mask() const;
+
+  /// @return A range that exactly covers the network.
+  IP4Range as_range() const;
+
+  /** Assign an @a addr and @a mask to @a this.
+   *
+   * @param addr Network addres.
+   * @param mask Network mask.
+   * @return @a this.
+   */
+  self_type &assign(IP4Addr const &addr, IPMask const &mask);
+
+  /// Reset network to invalid state.
+  self_type & clear() {  _mask.clear(); return *this;  }
+
+  /// Equality.
+  bool operator==(self_type const &that) const;
+
+  /// Inequality
+  bool operator!=(self_type const &that) const;
+
+protected:
+  IP4Addr _addr; ///< Network address (also lower_bound).
+  IPMask _mask;  ///< Network mask.
+};
+
+/// IPv6 network.
+class IP6Net {
+  using self_type = IP6Net; ///< Self reference type.
+public:
+  IP6Net()                      = default; ///< Construct invalid network.
+  IP6Net(self_type const &that) = default; ///< Copy constructor.
+
+  /** Construct from @a addr and @a mask.
+   *
+   * @param addr An address in the network.
+   * @param mask The mask for the network.
+   *
+   * The network is based on the mask, and the resulting network address is chosen such that the
+   * network will contain @a addr. For a given @a addr and @a mask there is only one network
+   * that satisifies these criteria.
+   */
+  IP6Net(IP6Addr addr, IPMask mask);
+
+  /** Parse network as @a text.
+   *
+   * @param text String describing the network in CIDR format.
+   * @return @c true if a valid string, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// @return @c true if the network is valid, @c false if not.
+  bool is_valid() const;
+
+  /// @return THh smallest address in the network.
+  IP6Addr lower_bound() const;
+
+  /// @return The largest address in the network.
+  IP6Addr upper_bound() const;
+
+  /// @return The mask for the network.
+  IPMask const &mask() const;
+
+  /// @return A range that exactly covers the network.
+  IP6Range as_range() const;
+
+  /** Assign an @a addr and @a mask to @a this.
+   *
+   * @param addr Network addres.
+   * @param mask Network mask.
+   * @return @a this.
+   */
+  self_type &assign(IP6Addr const &addr, IPMask const &mask);
+
+  /// Reset network to invalid state.
+  self_type &
+  clear() {
+    _mask.clear();
+    return *this;
+  }
+
+  /// Equality.
+  bool operator==(self_type const &that) const;
+
+  /// Inequality
+  bool operator!=(self_type const &that) const;
+
+protected:
+  IP6Addr _addr; ///< Network address (also lower_bound).
+  IPMask _mask;  ///< Network mask.
+};
+
+/** Representation of an IP address network.
+ *
+ */
+class IPNet {
+  using self_type = IPNet; ///< Self reference type.
+public:
+  IPNet()                      = default; ///< Construct invalid network.
+  IPNet(self_type const &that) = default; ///< Copy constructor.
+
+  /** Construct from @a addr and @a mask.
+   *
+   * @param addr An address in the network.
+   * @param mask The mask for the network.
+   *
+   * The network is based on the mask, and the resulting network address is chosen such that the
+   * network will contain @a addr. For a given @a addr and @a mask there is only one network
+   * that satisifies these criteria.
+   */
+  IPNet(IPAddr const &addr, IPMask const &mask);
+
+  IPNet(TextView text);
+
+  /** Parse network as @a text.
+   *
+   * @param text String describing the network in CIDR format.
+   * @return @c true if a valid string, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// @return @c true if the network is valid, @c false if not.
+  bool is_valid() const;
+
+  /// @return THh smallest address in the network.
+  IPAddr lower_bound() const;
+
+  /// @return The largest address in the network.
+  IPAddr upper_bound() const;
+
+  IPMask::raw_type width() const;
+
+  /// @return The mask for the network.
+  IPMask const &mask() const;
+
+  /// @return A range that exactly covers the network.
+  IPRange as_range() const;
+
+  bool is_ip4() const { return _addr.is_ip4(); }
+
+  bool is_ip6() const { return _addr.is_ip6(); }
+
+  sa_family_t family() const { return _addr.family(); }
+
+  IP4Net
+  ip4() const {
+    return IP4Net{_addr.ip4(), _mask};
+  }
+
+  IP6Net
+  ip6() const {
+    return IP6Net{_addr.ip6(), _mask};
+  }
+
+  /** Assign an @a addr and @a mask to @a this.
+   *
+   * @param addr Network addres.
+   * @param mask Network mask.
+   * @return @a this.
+   */
+  self_type &assign(IPAddr const &addr, IPMask const &mask);
+
+  /// Reset network to invalid state.
+  self_type &
+  clear() {
+    _mask.clear();
+    return *this;
+  }
+
+  /// Equality.
+  bool operator==(self_type const &that) const;
+
+  /// Inequality
+  bool operator!=(self_type const &that) const;
+
+protected:
+  IPAddr _addr; ///< Address and family.
+  IPMask _mask; ///< Network 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.
+  using value_type = std::tuple<IPRange const, PAYLOAD &>;
+
+  /// Construct an empty space.
+  IPSpace() = default;
+
+  /** Mark the range @a r with @a payload.
+   *
+   * @param range 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(IPRange const &range, 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);
+
+  /** Erase addresses in @a range.
+   *
+   * @param range Address range.
+   * @return @a this
+   */
+  self_type &erase(IPRange const &range);
+
+  /** Blend @a color in to the @a range.
+   *
+   * @tparam F Blending functor type (deduced).
+   * @tparam U Data to blend in to payloads.
+   * @param range Target 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 , 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 color,
+   * it is assigned the default constructed @c PAYLOAD blended with @a color. If the address has a
+   * @c PAYLOAD @a p, @a p is updated by invoking <tt>blender(p, color)</tt>, with the expectation
+   * that @a p will be updated in place.
+   */
+  template <typename F, typename U = PAYLOAD> self_type &blend(IPRange const &range, U const &color, F &&blender);
+
+  template <typename F, typename U = PAYLOAD>
+    self_type & blend(IP4Range const &range, U const &color, F &&blender);
+
+  template <typename F, typename U = PAYLOAD>
+    self_type &
+    blend(IP6Range const &range, U const &color, F &&blender);
+
+  /// @return The number of distinct ranges.
+  size_t
+  count() const {
+    return _ip4.count() + _ip6.count();
+  }
+
+  size_t
+  count_ip4() const {
+    return _ip4.count();
+  }
+  size_t
+  count_ip6() const {
+    return _ip6.count();
+  }
+
+  size_t count(sa_family_t f) const;
+
+  /// Remove all ranges.
+  void clear();
+
+  /** Constant iterator.
+   * The value type is a tuple of the IP address range and the @a PAYLOAD. Both are constant.
+   *
+   * @internal The non-const iterator is a subclass of this, in order to share implementation. This
+   * also makes it easy to convert from iterator to const iterator, which is desirable.
+   *
+   * @internal The return type is quite tricky because the value type of the nested containers is
+   * not the same as the value type for this container. It's not even a composite - @c IPRange is
+   * not an alias for either of the family specific range types. Therefore the iterator itself must
+   * contain a synthesized instance of the value type, which creates scoping and update problems.
+   * The approach here is to update the synthetic value when the iterator is modified and returning
+   * it by value for the dereference operator because a return by reference means  code like
+   * @code
+   *   auto && [ r , p ] = *(space.find(addr));
+   * @endcode
+   * can fail due to the iterator going out of scope after the statement is finished making @a r
+   * and @a p dangling references. If the return is by value the compiler takes care of it.
+   */
+  class const_iterator {
+    using self_type = const_iterator; ///< Self reference type.
+    friend class IPSpace;
+
+  public:
+    using value_type = std::tuple<IPRange const, PAYLOAD const &>; /// Import for API compliance.
+    // STL algorithm compliance.
+    using iterator_category = std::bidirectional_iterator_tag;
+    using pointer           = value_type *;
+    using reference         = value_type &;
+    using difference_type   = int;
+
+    /// Default constructor.
+    const_iterator() = default;
+
+    /// Copy constructor.
+    const_iterator(self_type const &that);
+
+    /// Assignment.
+    self_type &operator=(self_type const &that);
+
+    /// Pre-increment.
+    /// Move to the next element in the list.
+    /// @return The iterator.
+    self_type &operator++();
+
+    /// Pre-decrement.
+    /// Move to the previous element in the list.
+    /// @return The iterator.
+    self_type &operator--();
+
+    /// Post-increment.
+    /// Move to the next element in the list.
+    /// @return The iterator value before the increment.
+    self_type operator++(int);
+
+    /// Post-decrement.
+    /// Move to the previous element in the list.
+    /// @return The iterator value before the decrement.
+    self_type operator--(int);
+
+    /// Dereference.
+    /// @return A reference to the referent.
+    value_type operator*() const;
+
+    /// Dereference.
+    /// @return A pointer to the referent.
+    value_type const *operator->() const;
+
+    /// Equality
+    bool operator==(self_type const &that) const;
+
+    /// Inequality
+    bool operator!=(self_type const &that) const;
+
+  protected:
+    // These are stored non-const to make implementing @c iterator easier. The containing class provides the
+    // required @c const protection. Internally a tuple of iterators is stored for forward iteration. If
+    // the primary (ipv4) iterator is at the end, then use the secondary (ipv6) iterator. The reverse
+    // is done for reverse iteration. This depends on the extra support @c IntrusiveDList iterators
+    // provide.
+    typename IP4Space::iterator _iter_4; ///< IPv4 sub-space iterator.
+    typename IP6Space::iterator _iter_6; ///< IPv6 sub-space iterator.
+    /// Current value.
+    value_type _value{IPRange{}, *static_cast<PAYLOAD *>(detail::pseudo_nullptr)};
+
+    /** Internal constructor.
+     *
+     * @param iter4 Starting place for IPv4 subspace.
+     * @param iter6 Starting place for IPv6 subspace.
+     *
+     * In practice, at most one iterator should be "internal", the other should be the beginning or end.
+     */
+    const_iterator(typename IP4Space::iterator const &iter4, typename IP6Space::iterator const &iter6);
+  };
+
+  /** Iterator.
+   * The value type is a tuple of the IP address range and the @a PAYLOAD. The range is constant
+   * and the @a PAYLOAD is a reference. This can be used to update the @a PAYLOAD for this range.
+   *
+   * @note Range merges are not trigged by modifications of the @a PAYLOAD via an iterator.
+   */
+  class iterator : public const_iterator {
+    using self_type  = iterator;
+    using super_type = const_iterator;
+
+    friend class IPSpace;
+
+  protected:
+    using super_type::super_type; /// Inherit supertype constructors.
+    /// Protected constructor to convert const to non-const.
+    /// @note This makes for much less code duplication in iterator relevant methods.
+    iterator(const_iterator const& that) : const_iterator(that) {}
+  public:
+    /// Value type of iteration.
+    using value_type = std::tuple<IPRange const, PAYLOAD &>;
+    using pointer    = value_type *;
+    using reference  = value_type &;
+
+    /// Default constructor.
+    iterator() = default;
+
+    /// Copy constructor.
+    iterator(self_type const &that);
+
+    /// Assignment.
+    self_type &operator=(self_type const &that);
+
+    /// Pre-increment.
+    /// Move to the next element in the list.
+    /// @return The iterator.
+    self_type &operator++();
+
+    /// Pre-decrement.
+    /// Move to the previous element in the list.
+    /// @return The iterator.
+    self_type &operator--();
+
+    /// Post-increment.
+    /// Move to the next element in the list.
+    /// @return The iterator value before the increment.
+    self_type
+    operator++(int) {
+      self_type zret{*this};
+      ++*this;
+      return zret;
+    }
+
+    /// Post-decrement.
+    /// Move to the previous element in the list.
+    /// @return The iterator value before the decrement.
+    self_type
+    operator--(int) {
+      self_type zret{*this};
+      --*this;
+      return zret;
+    }
+
+    /// Dereference.
+    /// @return A reference to the referent.
+    value_type operator*() const;
+
+    /// Dereference.
+    /// @return A pointer to the referent.
+    value_type const *operator->() const;
+  };
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return Iterator for the range containing @a addr.
+   */
+  iterator find(IPAddr const &addr);
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return Iterator for the range containing @a addr.
+   */
+  const_iterator find(IPAddr const &addr) const;
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return An iterator which is valid if @a addr was found, @c end if not.
+   */
+  iterator find(IP4Addr const &addr);
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return An iterator which is valid if @a addr was found, @c end if not.
+   */
+  const_iterator find(IP4Addr const &addr) const;
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return An iterator which is valid if @a addr was found, @c end if not.
+   */
+  iterator find(IP6Addr const &addr);
+
+  /** Find the payload for an @a addr.
+   *
+   * @param addr Address to find.
+   * @return An iterator which is valid if @a addr was found, @c end if not.
+   */
+  const_iterator find(IP6Addr const &addr) const;
+
+  /// @return An iterator to the first element.
+  iterator begin();
+
+  /// @return A constant iterator to the first element.
+  const_iterator begin() const;
+
+  /// @return An iterator past the last element.
+  iterator end();
+
+  /// @return A constant iterator past the last element.
+  const_iterator end() const;
+
+  /// @return Iterator to the first IPv4 address.
+  iterator begin_ip4();
+  /// @return Iterator to the first IPv4 address.
+  const_iterator begin_ip4() const;
+
+  /// @return Iterator past the last IPv4 address.
+  iterator end_ip4();
+  /// @return Iterator past the last IPv4 address.
+  const_iterator end_ip4() const;
+
+  /// @return Iterator at the first IPv6 address.
+  iterator begin_ip6();
+  /// @return Iterator at the first IPv6 address.
+  const_iterator begin_ip6() const;
+  /// @return Iterator past the last IPv6 address.
+  iterator end_ip6();
+  /// @return Iterator past the last IPv6 address.
+  const_iterator end_ip6() const;
+
+  /// @return Iterator to the first address of @a family.
+  const_iterator begin(sa_family_t family) const;
+
+  /// @return Iterator past the last address of @a family.
+  const_iterator
+  end(sa_family_t family) const;
+
+protected:
+  IP4Space _ip4; ///< Sub-space containing IPv4 ranges.
+  IP6Space _ip6; ///< sub-space containing IPv6 ranges.
+};
+
+template <typename PAYLOAD>
+IPSpace<PAYLOAD>::const_iterator::const_iterator(typename IP4Space::iterator const &iter4, typename IP6Space::iterator const &iter6)
+: _iter_4(iter4), _iter_6(iter6) {
+  if (_iter_4.has_next()) {
+    new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
+  } else if (_iter_6.has_next()) {
+    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+  }
+}
+
+template <typename PAYLOAD> IPSpace<PAYLOAD>::const_iterator::const_iterator(self_type const &that) {
+  *this = that;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator=(self_type const &that) -> self_type & {
+  _iter_4 = that._iter_4;
+  _iter_6 = that._iter_6;
+  new (&_value) value_type{that._value};
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator++() -> self_type & {
+  bool incr_p = false;
+  if (_iter_4.has_next()) {
+    ++_iter_4;
+    incr_p = true;
+    if (_iter_4.has_next()) {
+      new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
+      return *this;
+    }
+  }
+
+  if (_iter_6.has_next()) {
+    if (incr_p || (++_iter_6).has_next()) {
+      new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+      return *this;
+    }
+  }
+  new (&_value) value_type{IPRange{}, *static_cast<PAYLOAD *>(detail::pseudo_nullptr)};
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator++(int) -> self_type {
+  self_type zret(*this);
+  ++*this;
+  return zret;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator--() -> self_type & {
+  if (_iter_6.has_prev()) {
+    --_iter_6;
+    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+    return *this;
+  }
+  if (_iter_4.has_prev()) {
+    --_iter_4;
+    new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
+    return *this;
+  }
+  new (&_value) value_type{IPRange{}, *static_cast<PAYLOAD *>(detail::pseudo_nullptr)};
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator--(int) -> self_type {
+  self_type zret(*this);
+  --*this;
+  return zret;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator*() const -> value_type {
+  return _value;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::const_iterator::operator->() const -> value_type const * {
+  return &_value;
+}
+
+/* Bit of subtlety with equality - although it seems that if @a _iter_4 is valid, it doesn't matter
+ * where @a _iter6 is (because it is really the iterator location that's being checked), it's
+ * neccesary to do the @a _iter_4 validity on both iterators to avoid the case of a false positive
+ * where different internal iterators are valid. However, in practice the other (non-active)
+ * iterator won't have an arbitrary value, it will be either @c begin or @c end in step with the
+ * active iterator therefore it's effective and cheaper to just check both values.
+ */
+
+template <typename PAYLOAD>
+bool
+IPSpace<PAYLOAD>::const_iterator::operator==(self_type const &that) const {
+  return _iter_4 == that._iter_4 && _iter_6 == that._iter_6;
+}
+
+template <typename PAYLOAD>
+bool
+IPSpace<PAYLOAD>::const_iterator::operator!=(self_type const &that) const {
+  return _iter_4 != that._iter_4 || _iter_6 != that._iter_6;
+}
+
+template <typename PAYLOAD> IPSpace<PAYLOAD>::iterator::iterator(self_type const &that) {
+  *this = that;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator=(self_type const &that) -> self_type & {
+  this->super_type::operator=(that);
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator->() const -> value_type const * {
+  return static_cast<value_type *>(&super_type::_value);
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator*() const -> value_type {
+  return reinterpret_cast<value_type const &>(super_type::_value);
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator++() -> self_type & {
+  this->super_type::operator++();
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::iterator::operator--() -> self_type & {
+  this->super_type::operator--();
+  return *this;
+}
+
+// --------------------------------------------------------------------------
+/// ------------------------------------------------------------------------------------
+
+// +++ IPRange +++
+
+inline IP4Range::IP4Range(string_view const &text) {
+  this->load(text);
+}
+
+inline auto
+IP4Range::networks() const -> NetSource {
+  return {NetSource{*this}};
+}
+
+inline IP6Range::IP6Range(string_view const &text) {
+  this->load(text);
+}
+
+inline auto
+IP6Range::networks() const -> NetSource {
+  return {NetSource{*this}};
+}
+
+inline IPRange::IPRange(IP4Range const &range) : _family(AF_INET) {
+  _range._ip4 = range;
+}
+
+inline IPRange::IPRange(IP6Range const &range) : _family(AF_INET6) {
+  _range._ip6 = range;
+}
+
+inline IPRange::IPRange(string_view const &text) {
+  this->load(text);
+}
+
+inline auto
+IPRange::networks() const -> NetSource {
+  return {NetSource{*this}};
+}
+
+inline bool
+IPRange::is(sa_family_t family) const {
+  return family == _family;
+}
+
+// +++ IPNet +++
+
+inline IP4Net::IP4Net(swoc::IP4Addr addr, swoc::IPMask mask) : _addr(addr & mask), _mask(mask) {}
+
+inline IPMask const &
+IP4Net::mask() const {
+  return _mask;
+}
+
+inline bool
+IP4Net::is_valid() const {
+  return _mask.is_valid();
+}
+
+inline IP4Addr
+IP4Net::lower_bound() const {
+  return _addr;
+}
+
+inline IP4Addr
+IP4Net::upper_bound() const {
+  return _addr | _mask;
+}
+
+inline IP4Range
+IP4Net::as_range() const {
+  return {this->lower_bound(), this->upper_bound()};
+}
+
+inline bool
+IP4Net::operator==(self_type const &that) const {
+  return _mask == that._mask && _addr == that._addr;
+}
+
+inline bool
+IP4Net::operator!=(self_type const &that) const {
+  return _mask != that._mask || _addr != that._addr;
+}
+
+inline IP4Net::self_type &
+IP4Net::assign(IP4Addr const &addr, IPMask const &mask) {
+  _addr = addr & mask;
+  _mask = mask;
+  return *this;
+}
+
+inline IP6Net::IP6Net(swoc::IP6Addr addr, swoc::IPMask mask) : _addr(addr & mask), _mask(mask) {}
+
+inline IPMask const &
+IP6Net::mask() const {
+  return _mask;
+}
+
+inline bool
+IP6Net::is_valid() const {
+  return _mask.is_valid();
+}
+
+inline IP6Addr
+IP6Net::lower_bound() const {
+  return _addr;
+}
+
+inline IP6Addr
+IP6Net::upper_bound() const {
+  return _addr | _mask;
+}
+
+inline IP6Range
+IP6Net::as_range() const {
+  return {this->lower_bound(), this->upper_bound()};
+}
+
+inline bool
+IP6Net::operator==(self_type const &that) const {
+  return _mask == that._mask && _addr == that._addr;
+}
+
+inline bool
+IP6Net::operator!=(self_type const &that) const {
+  return _mask != that._mask || _addr != that._addr;
+}
+
+inline IP6Net::self_type &
+IP6Net::assign(IP6Addr const &addr, IPMask const &mask) {
+  _addr = addr & mask;
+  _mask = mask;
+  return *this;
+}
+
+inline IPNet::IPNet(IPAddr const &addr, IPMask const &mask) : _addr(addr & mask), _mask(mask) {}
+
+inline IPNet::IPNet(TextView text) {
+  this->load(text);
+}
+
+inline bool
+IPNet::is_valid() const {
+  return _mask.is_valid();
+}
+
+inline IPAddr
+IPNet::lower_bound() const {
+  return _addr;
+}
+
+inline IPAddr
+IPNet::upper_bound() const {
+  return _addr | _mask;
+}
+
+inline IPMask::raw_type
+IPNet::width() const {
+  return _mask.width();
+}
+
+inline IPMask const &
+IPNet::mask() const {
+  return _mask;
+}
+
+inline IPRange
+IPNet::as_range() const {
+  return {this->lower_bound(), this->upper_bound()};
+}
+
+inline IPNet::self_type &
+IPNet::assign(IPAddr const &addr, IPMask const &mask) {
+  _addr = addr & mask;
+  _mask = mask;
+  return *this;
+}
+
+inline bool
+IPNet::operator==(IPNet::self_type const &that) const {
+  return _mask == that._mask && _addr == that._addr;
+}
+
+inline bool
+IPNet::operator!=(IPNet::self_type const &that) const {
+  return _mask != that._mask || _addr != that._addr;
+}
+
+inline bool
+operator==(IPNet const &lhs, IP4Net const &rhs) {
+  return lhs.is_ip4() && lhs.ip4() == rhs;
+}
+
+inline bool
+operator==(IP4Net const &lhs, IPNet const &rhs) {
+  return rhs.is_ip4() && rhs.ip4() == lhs;
+}
+
+inline bool
+operator==(IPNet const &lhs, IP6Net const &rhs) {
+  return lhs.is_ip6() && lhs.ip6() == rhs;
+}
+
+inline bool
+operator==(IP6Net const &lhs, IPNet const &rhs) {
+  return rhs.is_ip6() && rhs.ip6() == lhs;
+}
+
+// +++ Range -> Network classes +++
+
+inline bool
+IP4Range::NetSource::is_valid(swoc::IP4Addr mask) const {
+  return ((mask._addr & _range._min._addr) == _range._min._addr) && ((_range._min._addr | ~mask._addr) <= _range._max._addr);
+}
+
+inline IP4Net
+IP4Range::NetSource::operator*() const {
+  return IP4Net{_range.min(), IPMask{_cidr}};
+}
+
+inline IP4Range::NetSource::iterator
+IP4Range::NetSource::begin() const {
+  return *this;
+}
+
+inline IP4Range::NetSource::iterator
+IP4Range::NetSource::end() {
+  return self_type{range_type{}};
+}
+
+inline bool
+IP4Range::NetSource::empty() const {
+  return _range.empty();
+}
+
+inline IPMask
+IP4Range::NetSource::mask() const {
+  return IPMask{_cidr};
+}
+
+inline auto
+IP4Range::NetSource::operator->() -> self_type * {
+  return this;
+}
+
+inline IP4Addr const &
+IP4Range::NetSource::addr() const {
+  return _range.min();
+}
+
+inline bool
+IP4Range::NetSource::operator==(IP4Range::NetSource::self_type const &that) const {
+  return ((_cidr == that._cidr) && (_range == that._range)) || (_range.empty() && that._range.empty());
+}
+
+inline bool
+IP4Range::NetSource::operator!=(IP4Range::NetSource::self_type const &that) const {
+  return !(*this == that);
+}
+
+inline auto
+IP6Range::NetSource::begin() const -> iterator {
+  return *this;
+}
+
+inline auto
+IP6Range::NetSource::end() const -> iterator {
+  return self_type{range_type{}};
+}
+
+inline bool
+IP6Range::NetSource::empty() const {
+  return _range.empty();
+}
+
+inline IP6Net
+IP6Range::NetSource::operator*() const {
+  return IP6Net{_range.min(), _mask};
+}
+
+inline auto
+IP6Range::NetSource::operator->() -> self_type * {
+  return this;
+}
+
+inline bool
+IP6Range::NetSource::is_valid(IPMask const &mask) {
+  return ((_range.min() & mask) == _range.min()) && ((_range.min() | mask) <= _range.max());
+}
+
+inline bool
+IP6Range::NetSource::operator==(IP6Range::NetSource::self_type const &that) const {
+  return ((_mask == that._mask) && (_range == that._range)) || (_range.empty() && that._range.empty());
+}
+
+inline bool
+IP6Range::NetSource::operator!=(IP6Range::NetSource::self_type const &that) const {
+  return !(*this == that);
+}
+
+inline IP6Addr const &
+IP6Range::NetSource::addr() const {
+  return _range.min();
+}
+
+inline IPMask
+IP6Range::NetSource::mask() const {
+  return _mask;
+}
+
+inline IPRange::NetSource::NetSource(IPRange::NetSource::range_type const &range) {
+  if (range.is_ip4()) {
+    new (&_ip4) decltype(_ip4)(range.ip4());
+    _family = AF_INET;
+  } else if (range.is_ip6()) {
+    new (&_ip6) decltype(_ip6)(range.ip6());
+    _family = AF_INET6;
+  }
+}
+
+inline auto
+IPRange::NetSource::begin() const -> iterator {
+  return *this;
+}
+
+inline auto
+IPRange::NetSource::end() const -> iterator {
+  return AF_INET == _family ? self_type{IP4Range{}} : AF_INET6 == _family ? self_type{IP6Range{}} : self_type{IPRange{}};
+}
+
+inline IPAddr
+IPRange::NetSource::addr() const {
+  if (AF_INET == _family) {
+    return _ip4.addr();
+  } else if (AF_INET6 == _family) {
+    return _ip6.addr();
+  }
+  return {};
+}
+
+inline IPMask
+IPRange::NetSource::mask() const {
+  if (AF_INET == _family) {
+    return _ip4.mask();
+  } else if (AF_INET6 == _family) {
+    return _ip6.mask();
+  }
+  return {};
+}
+
+inline IPNet
+IPRange::NetSource::operator*() const {
+  return {this->addr(), this->mask()};
+}
+
+inline auto
+IPRange::NetSource::operator++() -> self_type & {
+  if (AF_INET == _family) {
+    ++_ip4;
+  } else if (AF_INET6 == _family) {
+    ++_ip6;
+  }
+  return *this;
+}
+
+inline auto
+IPRange::NetSource::operator->() -> self_type * {
+  return this;
+}
+
+inline bool
+IPRange::NetSource::operator==(self_type const &that) const {
+  if (_family != that._family) {
+    return false;
+  }
+  if (AF_INET == _family) {
+    return _ip4 == that._ip4;
+  } else if (AF_INET6 == _family) {
+    return _ip6 == that._ip6;
+  } else if (AF_UNSPEC == _family) {
+    return true;
+  }
+  return false;
+}
+
+inline bool
+IPRange::NetSource::operator!=(self_type const &that) const {
+  return !(*this == that);
+}
+
+// --- IPSpace
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::mark(IPRange const &range, PAYLOAD const &payload) -> self_type & {
+  if (range.is(AF_INET)) {
+    _ip4.mark(range.ip4(), payload);
+  } else if (range.is(AF_INET6)) {
+    _ip6.mark(range.ip6(), payload);
+  }
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::fill(IPRange const &range, PAYLOAD const &payload) -> self_type & {
+  if (range.is(AF_INET6)) {
+    _ip6.fill(range.ip6(), payload);
+  } else if (range.is(AF_INET)) {
+    _ip4.fill(range.ip4(), payload);
+  }
+  return *this;
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::erase(IPRange const &range) -> self_type & {
+  if (range.is(AF_INET)) {
+    _ip4.erase(range.ip4());
+  } else if (range.is(AF_INET6)) {
+    _ip6.erase(range.ip6());
+  }
+  return *this;
+}
+
+template <typename PAYLOAD>
+template <typename F, typename U>
+auto
+IPSpace<PAYLOAD>::blend(IPRange const &range, U const &color, F &&blender) -> self_type & {
+  if (range.is(AF_INET)) {
+    _ip4.blend(range.ip4(), color, blender);
+  } else if (range.is(AF_INET6)) {
+    _ip6.blend(range.ip6(), color, blender);
+  }
+  return *this;
+}
+
+template <typename PAYLOAD>
+template <typename F, typename U>
+auto
+IPSpace<PAYLOAD>::blend(IP4Range const &range, U const &color, F &&blender) -> self_type & {
+  _ip4.blend(range, color, std::forward<F>(blender));
+  return *this;
+}
+
+template <typename PAYLOAD>
+template <typename F, typename U>
+auto
+IPSpace<PAYLOAD>::blend(IP6Range const &range, U const &color, F &&blender) -> self_type & {
+  _ip6.blend(range, color, std::forward<F>(blender));
+  return *this;
+}
+
+template <typename PAYLOAD>
+void
+IPSpace<PAYLOAD>::clear() {
+  _ip4.clear();
+  _ip6.clear();
+}
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::begin() -> iterator { return iterator{_ip4.begin(), _ip6.begin()}; }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::begin() const -> const_iterator { return const_cast<self_type *>(this)->begin(); }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::end() -> iterator { return iterator{_ip4.end(), _ip6.end()}; }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::end() const -> const_iterator { return const_cast<self_type *>(this)->end(); }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::begin_ip4() -> iterator { return this->begin(); }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::begin_ip4() const -> const_iterator { return this->begin(); }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::end_ip4() -> iterator { return { _ip4.end(), _ip6.begin() }; }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::end_ip4() const -> const_iterator { return const_cast<self_type*>(this)->end_ip4(); }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::begin_ip6() -> iterator {
+  return { _ip4.end(), _ip6.begin() };
+}
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::begin_ip6() const -> const_iterator {
+  return const_cast<self_type*>(this)->begin_ip6();
+}
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::end_ip6() -> iterator { return this->end(); }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::end_ip6() const -> const_iterator { return this->end(); }
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::find(IPAddr const &addr) -> iterator {
+  if (addr.is_ip4()) {
+    return this->find(addr.ip4());
+  } else if (addr.is_ip6()) {
+    return this->find(addr.ip6());
+  }
+  return this->end();
+}
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::find(IPAddr const &addr) const -> const_iterator {
+  return const_cast<self_type *>(this)->find(addr);
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::find(IP4Addr const &addr) -> iterator {
+  if ( auto spot = _ip4.find(addr) ; spot != _ip4.end()) {
+    return { spot, _ip6.begin() };
+  }
+  return this->end();
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::find(IP4Addr const &addr) const -> const_iterator {
+  return const_cast<self_type *>(this)->find(addr);
+}
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::find(IP6Addr const &addr) -> iterator { return {_ip4.end(), _ip6.find(addr)}; }
+
+template <typename PAYLOAD>
+auto IPSpace<PAYLOAD>::find(IP6Addr const &addr) const -> const_iterator { return {_ip4.end(), _ip6.find(addr)}; }
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::begin(sa_family_t family) const -> const_iterator {
+  if (AF_INET == family) {
+    return this->begin_ip4();
+  } else if (AF_INET6 == family) {
+    return this->begin_ip6();
+  }
+  return this->end();
+}
+
+template <typename PAYLOAD>
+auto
+IPSpace<PAYLOAD>::end(sa_family_t family) const -> const_iterator {
+  if (AF_INET == family) {
+    return this->end_ip4();
+  } else if (AF_INET6 == family) {
+    return this->end_ip6();
+  }
+  return this->end();
+}
+
+template <typename PAYLOAD>
+size_t
+IPSpace<PAYLOAD>::count(sa_family_t f) const {
+  return IP4Addr::AF_value == f ? _ip4.count() : IP6Addr::AF_value == f ? _ip6.count() : 0;
+}
+
+}} // namespace swoc::SWOC_VERSION_NS
+
+/// @cond NOT_DOCUMENTED
+namespace std {
+
+// -- Tuple support for IP4Net --
+template <> class tuple_size<swoc::IP4Net> : public std::integral_constant<size_t, 2> {};
+
+template <size_t IDX> class tuple_element<IDX, swoc::IP4Net> { static_assert("swoc::IP4Net tuple index out of range"); };
+
+template <> class tuple_element<0, swoc::IP4Net> {
+public:
+  using type = swoc::IP4Addr;
+};
+
+template <> class tuple_element<1, swoc::IP4Net> {
+public:
+  using type = swoc::IPMask;
+};
+
+// -- Tuple support for IP6Net --
+template <> class tuple_size<swoc::IP6Net> : public std::integral_constant<size_t, 2> {};
+
+template <size_t IDX> class tuple_element<IDX, swoc::IP6Net> { static_assert("swoc::IP6Net tuple index out of range"); };
+
+template <> class tuple_element<0, swoc::IP6Net> {
+public:
+  using type = swoc::IP6Addr;
+};
+
+template <> class tuple_element<1, swoc::IP6Net> {
+public:
+  using type = swoc::IPMask;
+};
+
+// -- Tuple support for IPNet --
+template <> class tuple_size<swoc::IPNet> : public std::integral_constant<size_t, 2> {};
+
+template <size_t IDX> class tuple_element<IDX, swoc::IPNet> { static_assert("swoc::IPNet tuple index out of range"); };
+
+template <> class tuple_element<0, swoc::IPNet> {
+public:
+  using type = swoc::IPAddr;
+};
+
+template <> class tuple_element<1, swoc::IPNet> {
+public:
+  using type = swoc::IPMask;
+};
+
+} // namespace std
+
+namespace swoc { inline namespace SWOC_VERSION_NS {
+
+template <size_t IDX>
+typename std::tuple_element<IDX, IP4Net>::type
+get(swoc::IP4Net const &net) {
+  if constexpr (IDX == 0) {
+    return net.lower_bound();
+  } else if constexpr (IDX == 1) {
+    return net.mask();
+  }
+}
+
+template <size_t IDX>
+typename std::tuple_element<IDX, IP6Net>::type
+get(swoc::IP6Net const &net) {
+  if constexpr (IDX == 0) {
+    return net.lower_bound();
+  } else if constexpr (IDX == 1) {
+    return net.mask();
+  }
+}
+
+template <size_t IDX>
+typename std::tuple_element<IDX, IPNet>::type
+get(swoc::IPNet const &net) {
+  if constexpr (IDX == 0) {
+    return net.lower_bound();
+  } else if constexpr (IDX == 1) {
+    return net.mask();
+  }
+}
+/// @endcond
+
+}} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/IPSrv.h b/lib/swoc/include/swoc/IPSrv.h
new file mode 100644
index 0000000..e0e4d48
--- /dev/null
+++ b/lib/swoc/include/swoc/IPSrv.h
@@ -0,0 +1,680 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Network Geographics 2014
+/** @file
+   IP address and network related classes.
+ */
+
+#pragma once
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include "swoc/swoc_version.h"
+#include "swoc/IPAddr.h"
+
+namespace swoc { inline namespace SWOC_VERSION_NS {
+
+class IPSrv;
+
+/// An IPv4 address and host_order_port, modeled on an SRV type for DNS.
+class IP4Srv {
+private:
+  using self_type = IP4Srv;
+
+public:
+  constexpr IP4Srv() = default; ///< Default constructor.
+
+  /** Construct from address and host_order_port.
+   *
+   * @param addr The address.
+   * @param port The port in host order, defaults to 0.
+   */
+  explicit IP4Srv(IP4Addr addr, in_port_t port = 0);
+
+  /** Construct from generic.
+   *
+   * @param that The generic SRV.
+   *
+   * If @a that is not IPv4 the result is a default constructed instance.
+   */
+  explicit IP4Srv(IPSrv const& that);
+
+  /** Construct from socket address.
+   *
+   * @param sa Socket address.
+   */
+  IP4Srv(sockaddr_in const * s) : _addr(s), _port(ntohs(s->sin_port)) {}
+
+  /** Construct from a string.
+   *
+   * @param text Input text.
+   *
+   * If the port is not present it is set to zero.
+   */
+  explicit IP4Srv(swoc::TextView text);
+
+  /// Implicit conversion to an address.
+  constexpr operator IP4Addr const& () const;
+
+  /// @return The address.
+  constexpr IP4Addr const& addr() const;
+
+  /// @return The host_order_port in host order.
+  in_port_t host_order_port() const;
+
+  /// @return The host_order_port in network order.
+  in_port_t network_order_port() const;
+
+  /// The protocol family.
+  /// @return @c AF_INET
+  /// @note Useful primarily for template classes.
+  static constexpr sa_family_t family();
+
+  bool operator == (self_type that) const;
+  bool operator != (self_type that) const;
+
+  bool operator < (self_type that) const;
+  bool operator <= (self_type const& that) const;
+  bool operator > (self_type const& that) const;
+  bool operator >= (self_type const& that) const;
+
+  /** Load from a string.
+   *
+   * @param text Input string.
+   * @return @c true if @a text in a valid format, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /** Change the address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type & assign(IP4Addr const& addr);
+
+  /** Change the host_order_port.
+   *
+   * @param port Port to assign.
+   * @return @a this.
+   */
+  self_type & assign(in_port_t port);
+
+  /** Change the address and port.
+   *
+   * @param addr Address to assign.
+   * @param port Port to assign.
+   * @return @a this
+   */
+  self_type & assign(IP4Addr const& addr, in_port_t port);
+
+  /** Change the address and port.
+   *
+   * @param s A socket address.
+   * @return @a this
+   */
+  self_type & assign(sockaddr_in const * s);
+
+protected:
+  IP4Addr _addr; ///< Address.
+  in_port_t _port = 0; ///< Port.
+};
+
+/// An IPv6 address and host_order_port, modeled on an SRV type for DNS.
+class IP6Srv {
+private:
+  using self_type = IP6Srv;
+
+public:
+  IP6Srv() = default; ///< Default constructor.
+
+  /** Construct from address and host_order_port.
+   *
+   * @param addr The address.
+   * @param port The port in host order, defaults to 0.
+   */
+  explicit IP6Srv(IP6Addr addr, in_port_t port = 0);
+
+  /** Construct from generic.
+   *
+   * @param that The generic SRV.
+   *
+   * If @a that is not IPv6 the result is a default constructed instance.
+   */
+  explicit IP6Srv(IPSrv const& that);
+
+  /** Construct from a socket address.
+   *
+   * @param s Socket address.
+   */
+  explicit IP6Srv(sockaddr_in6 const * s);
+
+  /** Construct from a string.
+   *
+   * @param text Input text.
+   *
+   * If the port is not present it is set to zero.
+   */
+  explicit IP6Srv(swoc::TextView text);
+
+  /** Load from a string.
+   *
+   * @param text Input string.
+   * @return @c true if @a text in a valid format, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// Implicit conversion to address.
+  constexpr operator IP6Addr const& () const;
+
+  /// @return The address.
+  constexpr IP6Addr const& addr() const;
+  /// @return The port in host order.
+  in_port_t host_order_port() const;
+  /// @return The port in network order.
+  in_port_t network_order_port() const;
+
+  /// The protocol family.
+  /// @return @c AF_INET6
+  /// @note Useful primarily for template classes.
+  static constexpr sa_family_t family();
+
+  bool operator == (self_type that) const;
+  bool operator != (self_type that) const;
+
+  bool operator < (self_type that) const;
+  bool operator <= (self_type const& that) const;
+  bool operator > (self_type const& that) const;
+  bool operator >= (self_type const& that) const;
+
+  /** Change the address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type & assign(IP6Addr const& addr);
+
+  /** Change the host_order_port.
+   *
+   * @param port Port to assign.
+   * @return @a this.
+   */
+  self_type & assign(in_port_t port);
+
+  /** Change the address and host_order_port.
+   *
+   * @param addr Address to assign.
+   * @param port Port to assign.
+   * @return @a this
+   */
+  self_type & assign(IP6Addr const& addr, in_port_t port);
+
+  /** Change the address and port.
+   *
+   * @param s A socket address.
+   * @return @a this
+   */
+  self_type & assign(sockaddr_in6 const * s);
+
+protected:
+  IP6Addr _addr; ///< Address.
+  in_port_t _port = 0; ///< Port.
+};
+
+/// An IP address and host_order_port, modeled on an SRV type for DNS.
+class IPSrv {
+private:
+  using self_type = IPSrv;
+
+public:
+  IPSrv() = default; ///< Default constructor.
+  explicit IPSrv(IP4Addr addr, in_port_t port = 0) : _srv(IP4Srv{addr, port}), _family(addr.family()) {}
+  explicit IPSrv(IP6Addr addr, in_port_t port = 0) : _srv(IP6Srv{addr, port}), _family(addr.family()) {}
+  explicit IPSrv(IPAddr addr, in_port_t port = 0);
+  explicit IPSrv(sockaddr const * sa);
+  explicit IPSrv(sockaddr_in const * s);
+  explicit IPSrv(sockaddr_in6 const * s);
+  explicit IPSrv(IPEndpoint const& ep);
+
+  /** Construct from a string.
+   *
+   * @param text Input text.
+   *
+   * If the port is not present it is set to zero.
+   */
+  explicit IPSrv(swoc::TextView text);
+
+  /** Load from a string.
+   *
+   * @param text Input string.
+   * @return @c true if @a text in a valid format, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// @return The address.
+  IPAddr addr() const;
+  /// @return The host_order_port in host order..
+  constexpr in_port_t host_order_port() const;
+  /// @return The host_order_port in network order.
+  in_port_t network_order_port() const;
+  /// @return The protocol of the current value.
+  constexpr sa_family_t family() const;
+
+  /// @return @c true if the data is IPv4, @c false if not.
+  bool is_ip4() const;
+  /// @return @c true if hte data is IPv6, @c false if not.
+  bool is_ip6() const;
+
+  /// @return The IPv4 data.
+  IP4Srv const& ip4() const { return _srv._ip4; }
+  /// @return The IPv6 data.
+  IP6Srv const& ip6() const { return _srv._ip6; }
+
+  /** Change the address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type & assign(IP4Addr const& addr);
+
+  /** Change the address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type & assign(IP6Addr const& addr);
+
+  /** Change the address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   *
+   * If @a addr isn't valid then no assignment is made.
+   */
+  self_type & assign(IPAddr const& addr);
+
+  /** Change the host_order_port.
+   *
+   * @param port Port in host order.
+   * @return @a this.
+   */
+  self_type & assign(in_port_t port);
+
+  /** Change the address and port.
+   *
+   * @param addr Address to assign.
+   * @param port Port to assign.
+   * @return @a this
+   */
+  self_type & assign(IP4Addr const& addr, in_port_t port);
+
+  /** Change the address and port.
+   *
+   * @param addr Address to assign.
+   * @param port Port to assign.
+   * @return @a this
+   */
+  self_type & assign(IP6Addr const& addr, in_port_t port);
+
+  /** Change the address and port.
+   *
+   * @param sa Socket address.
+   * @return @a this
+   */
+  self_type & assign(sockaddr const * sa);
+
+  /** Change the address and port.
+   *
+   * @param s Socket address.
+   * @return @a this
+   */
+  self_type & assign(sockaddr_in const * s);
+
+  /** Change the address and port.
+   *
+   * @param s Socket address.
+   * @return @a this
+   */
+  self_type & assign(sockaddr_in6 const * s);
+
+  /** Change the address and host_order_port.
+   *
+   * @param addr Address to assign.
+   * @param port Port to assign.
+   * @return @a this
+   *
+   * If @a addr isn't valid then no assignment is made.
+   */
+  self_type & assign(IPAddr const& addr, in_port_t port);
+
+  self_type & operator = (self_type const& that) = default;
+  self_type & operator = (IP4Srv const& that);
+  self_type & operator = (IP6Srv const& that);
+  self_type & operator = (sockaddr const * sa) { return this->assign(sa); }
+  self_type & operator = (sockaddr_in const * s) { return this->assign(s); }
+  self_type & operator = (sockaddr_in6 const * s) { return this->assign(s); }
+
+protected:
+  /// Family specialized data.
+  union data {
+    IP4Srv _ip4;            ///< IPv4 address (host)
+    IP6Srv _ip6;            ///< IPv6 address (host)
+
+    data() {}; // enable default construction.
+
+    /// Construct from IPv4 data.
+    explicit data(IP4Srv const &srv) : _ip4(srv) {}
+    explicit data(sockaddr_in const * s) : _ip4(s) {}
+
+    /// Construct from IPv6 data.
+    explicit data(IP6Srv const &srv) : _ip6(srv) {}
+    explicit data(sockaddr_in6 const * s) : _ip6(s) {}
+
+    /// @return A generic address.
+    IPAddr addr(sa_family_t f) const;
+
+    /// @return The port in host order.
+    constexpr in_port_t port(sa_family_t f) const;
+  } _srv;
+
+  sa_family_t _family = AF_UNSPEC; ///< Protocol family.
+
+};
+
+// --- Implementation
+
+inline IP4Srv::IP4Srv(IP4Addr addr, in_port_t port) : _addr(addr), _port(port) {}
+inline IP4Srv::IP4Srv(IPSrv const &that) : IP4Srv(that.is_ip4() ? that.ip4() : self_type{}) {}
+
+inline auto IP4Srv::assign(IP4Addr const &addr) -> self_type & { _addr = addr; return *this; }
+inline auto IP4Srv::assign(in_port_t port) -> self_type & { _port = port; return *this; }
+inline auto IP4Srv::assign(IP4Addr const &addr, in_port_t port) -> self_type & { _addr = addr; _port = port; return *this;}
+inline auto IP4Srv::assign(sockaddr_in const * s) -> self_type & { _addr = s; _port = ntohs(s->sin_port); return *this; }
+inline constexpr IP4Srv::operator IP4Addr const &() const { return _addr; }
+inline constexpr IP4Addr const & IP4Srv::addr() const { return _addr; }
+inline in_port_t IP4Srv::host_order_port() const { return _port; }
+inline in_port_t IP4Srv::network_order_port() const { return htons(_port); }
+inline constexpr sa_family_t IP4Srv::family() { return AF_INET; }
+
+inline bool IP4Srv::operator==(IP4Srv::self_type that) const { return _addr == that._addr && _port == that._port; }
+inline bool IP4Srv::operator!=(IP4Srv::self_type that) const { return _addr != that._addr || _port != that._port; }
+inline bool IP4Srv::operator< (IP4Srv::self_type that) const { return _addr < that._addr || (_addr == that._addr && _port < that._port); }
+inline bool IP4Srv::operator<=(IP4Srv::self_type const &that) const { return _addr < that._addr || (_addr == that._addr && _port <= that._port); }
+inline bool IP4Srv::operator> (IP4Srv::self_type const &that) const { return that < *this; }
+inline bool IP4Srv::operator>=(IP4Srv::self_type const &that) const { return that <= *this; }
+
+/// --- IPv6
+
+inline IP6Srv::IP6Srv(IP6Addr addr, in_port_t port) : _addr(addr), _port(port) {}
+inline IP6Srv::IP6Srv(IPSrv const &that) : IP6Srv(that.is_ip6() ? that.ip6() : self_type{}) {}
+inline IP6Srv::IP6Srv(const sockaddr_in6 *s) : _addr(s->sin6_addr), _port(ntohs(s->sin6_port)) {}
+
+inline constexpr IP6Srv::operator IP6Addr const &() const { return _addr; }
+inline constexpr IP6Addr const & IP6Srv::addr() const { return _addr; }
+inline in_port_t IP6Srv::host_order_port() const { return _port; }
+inline in_port_t IP6Srv::network_order_port() const { return htons(_port); }
+inline constexpr sa_family_t IP6Srv::family() { return AF_INET6; }
+
+inline bool IP6Srv::operator==(IP6Srv::self_type that) const { return _addr == that._addr && _port == that._port; }
+inline bool IP6Srv::operator!=(IP6Srv::self_type that) const { return _port != that._port || _addr != that._addr; }
+inline bool IP6Srv::operator< (IP6Srv::self_type that) const { return _addr < that._addr || (_addr == that._addr && _port < that._port); }
+inline bool IP6Srv::operator<=(IP6Srv::self_type const &that) const { return _addr < that._addr || (_addr == that._addr && _port <= that._port); }
+inline bool IP6Srv::operator> (IP6Srv::self_type const &that) const { return that < *this; }
+inline bool IP6Srv::operator>=(IP6Srv::self_type const &that) const { return that <= *this; }
+
+inline auto
+IP6Srv::assign(in_port_t port) -> self_type & { _port = port; return *this; }
+
+inline auto
+IP6Srv::assign(IP6Addr const &addr) -> self_type & { _addr = addr; return *this; }
+
+inline auto
+IP6Srv::assign(IP6Addr const &addr, in_port_t port) -> self_type & {
+  _addr = addr;
+  _port = port;
+  return *this;
+}
+
+inline auto
+IP6Srv::assign(sockaddr_in6 const *s) -> self_type & {
+  _addr = s;
+  _port = ntohs(s->sin6_port);
+  return *this;
+}
+
+// --- Generic SRV
+
+inline IPSrv::IPSrv(const sockaddr_in *s) : _srv(s), _family(AF_INET) {}
+inline IPSrv::IPSrv(const sockaddr_in6 *s) : _srv(s), _family(AF_INET6) {}
+inline IPSrv::IPSrv(const sockaddr *sa) { this->assign(sa); }
+
+inline IPAddr IPSrv::addr() const { return _srv.addr(_family); }
+inline constexpr sa_family_t IPSrv::family() const { return _family; }
+inline bool IPSrv::is_ip4() const { return _family == AF_INET; }
+inline bool IPSrv::is_ip6() const { return _family == AF_INET6; }
+
+inline auto
+IPSrv::assign(IP6Addr const &addr) -> self_type & {
+  _srv._ip6.assign(addr, this->host_order_port());
+  _family = addr.family();
+  return *this;
+}
+
+inline auto
+IPSrv::assign(IP4Addr const &addr, in_port_t port) -> self_type & {
+  _srv._ip4.assign(addr, port);
+  _family = addr.family();
+  return *this;
+}
+
+inline auto
+IPSrv::assign(IP6Addr const &addr, in_port_t port) -> self_type & {
+  _srv._ip6.assign(addr, port);
+  _family = addr.family();
+  return *this;
+}
+
+inline auto
+IPSrv::assign(IP4Addr const &addr) -> self_type & {
+  _srv._ip4.assign(addr, this->host_order_port());
+  _family = addr.family();
+  return *this;
+}
+
+inline auto
+IPSrv::assign(in_port_t port) -> self_type & {
+  if (this->is_ip4()) { _srv._ip4.assign(port); }
+  else if (this->is_ip6()) { _srv._ip6.assign(port); }
+  return *this;
+}
+
+inline auto
+IPSrv::assign(IPAddr const &addr) -> self_type & {
+  if (addr.is_ip4()) { this->assign(addr.ip4()); }
+  else if (addr.is_ip6()) { this->assign(addr.ip6());}
+  return *this;
+}
+
+inline auto
+IPSrv::assign(IPAddr const &addr, in_port_t port) -> self_type & {
+  if (addr.is_ip4()) { this->assign(addr.ip4(), port); _family = addr.family(); }
+  else if (addr.is_ip6()) { this->assign(addr.ip6(), port); _family = addr.family(); }
+  return *this;
+}
+
+inline auto
+IPSrv::operator=(IP4Srv const &that) -> self_type & {
+  _family = that.family();
+  _srv._ip4 = that;
+  return *this;
+}
+
+inline auto
+IPSrv::operator=(IP6Srv const &that) -> self_type & {
+  _family = that.family();
+  _srv._ip6 = that;
+  return *this;
+}
+
+inline auto
+IPSrv::assign(sockaddr_in const * s) -> self_type & {
+  _family = _srv._ip4.family();
+  _srv._ip4.assign(s);
+  return *this;
+}
+
+inline auto
+IPSrv::assign(sockaddr_in6 const * s) -> self_type & {
+  _family = _srv._ip6.family();
+  _srv._ip6.assign(s);
+  return *this;
+}
+
+inline constexpr in_port_t
+IPSrv::host_order_port() const { return _srv.port(_family); }
+
+inline in_port_t
+IPSrv::network_order_port() const { return ntohs(_srv.port(_family)); }
+
+inline IPAddr
+IPSrv::data::addr(sa_family_t f) const {
+  return (f == AF_INET) ? _ip4.addr() : (f == AF_INET6) ? _ip6.addr() : IPAddr::INVALID;
+}
+
+constexpr inline in_port_t
+IPSrv::data::port(sa_family_t f) const {
+  return (f == AF_INET) ? _ip4.host_order_port() : (f == AF_INET6) ? _ip6.host_order_port() : 0;
+}
+// --- Independent comparisons.
+
+inline bool operator == (IPSrv const& lhs, IP4Srv const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() == rhs;
+}
+
+inline bool operator == (IP4Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip4() && rhs.ip4() == lhs;
+}
+
+inline bool operator != (IPSrv const& lhs, IP4Srv const& rhs) {
+  return ! lhs.is_ip4() || lhs.ip4() != rhs;
+}
+
+inline bool operator != (IP4Srv const& lhs, IPSrv const& rhs) {
+  return ! rhs.is_ip4() || rhs.ip4() != lhs;
+}
+
+inline bool operator <  (IPSrv const& lhs, IP4Srv const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() < rhs;
+}
+
+inline bool operator <  (IP4Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip4() && lhs < rhs.ip4();
+}
+
+inline bool operator <= (IPSrv const& lhs, IP4Srv const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() <= rhs;
+}
+
+inline bool operator <= (IP4Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip4() && lhs <= rhs.ip4();
+}
+
+inline bool operator >  (IPSrv const& lhs, IP4Srv const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() > rhs;
+}
+
+inline bool operator >  (IP4Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip4() && lhs > rhs.ip4();
+}
+
+inline bool operator >= (IPSrv const& lhs, IP4Srv const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() >= rhs;
+}
+
+inline bool operator >= (IP4Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip4() && lhs >= rhs.ip4();
+}
+
+
+inline bool operator == (IPSrv const& lhs, IP6Srv const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() == rhs;
+}
+
+inline bool operator == (IP6Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip6() && rhs.ip6() == lhs;
+}
+
+inline bool operator != (IPSrv const& lhs, IP6Srv const& rhs) {
+  return ! lhs.is_ip6() || lhs.ip6() != rhs;
+}
+
+inline bool operator != (IP6Srv const& lhs, IPSrv const& rhs) {
+  return ! rhs.is_ip6() || rhs.ip6() != lhs;
+}
+
+inline bool operator <  (IPSrv const& lhs, IP6Srv const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() < rhs;
+}
+
+inline bool operator <  (IP6Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip6() && lhs < rhs.ip6();
+}
+
+inline bool operator <= (IPSrv const& lhs, IP6Srv const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() <= rhs;
+}
+
+inline bool operator <= (IP6Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip6() && lhs <= rhs.ip6();
+}
+
+inline bool operator >  (IPSrv const& lhs, IP6Srv const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() > rhs;
+}
+
+inline bool operator >  (IP6Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip6() && lhs > rhs.ip6();
+}
+
+inline bool operator >= (IPSrv const& lhs, IP6Srv const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() >= rhs;
+}
+
+inline bool operator >= (IP6Srv const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip6() && lhs >= rhs.ip6();
+}
+
+// --- Cross address equality
+
+inline bool operator == (IPSrv const& lhs, IP4Addr const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() == rhs;
+}
+
+inline bool operator == (IP4Addr const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip4() && lhs == rhs.ip4();
+}
+
+inline bool operator != (IPSrv const& lhs, IP4Addr const& rhs) {
+  return ! lhs.is_ip4() || lhs.ip4() != rhs;
+}
+
+inline bool operator != (IP4Addr const& lhs, IPSrv const& rhs) {
+  return ! rhs.is_ip4() || lhs != rhs.ip4();
+}
+
+inline bool operator == (IPSrv const& lhs, IP6Addr const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() == rhs;
+}
+
+inline bool operator == (IP6Addr const& lhs, IPSrv const& rhs) {
+  return rhs.is_ip6() && lhs == rhs.ip6();
+}
+
+inline bool operator != (IPSrv const& lhs, IP6Addr const& rhs) {
+  return ! lhs.is_ip6() || lhs.ip6() != rhs;
+}
+
+inline bool operator != (IP6Addr const& lhs, IPSrv const& rhs) {
+  return ! rhs.is_ip6() || lhs != rhs.ip6();
+}
+
+}} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/IntrusiveDList.h b/lib/swoc/include/swoc/IntrusiveDList.h
index 3efd678..0287a2c 100644
--- a/lib/swoc/include/swoc/IntrusiveDList.h
+++ b/lib/swoc/include/swoc/IntrusiveDList.h
@@ -878,6 +878,7 @@
 }
 
 namespace detail {
+/// @cond INTERNAL_DETAIL
 // Make @c apply more convenient by allowing the function to take a reference type or pointer type
 // to the container elements. The pointer type is the base, plus a shim to convert from a reference
 // type functor to a pointer pointer type. The complex return type definition forces only one, but
@@ -911,5 +912,5 @@
 IntrusiveDList<L>::apply(F &&f) -> self_type & {
   return detail::Intrusive_DList_Apply(*this, f);
 }
-
+/// @endcond
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/Lexicon.h b/lib/swoc/include/swoc/Lexicon.h
index 90ea861..df5c112 100644
--- a/lib/swoc/include/swoc/Lexicon.h
+++ b/lib/swoc/include/swoc/Lexicon.h
@@ -40,12 +40,12 @@
 } // namespace detail
 
 /// Policy template use to specify the hash function for the integral type of @c Lexicon.
-/// The default is to cast to the required hash value type, which is usually sufficient.
-/// In some cases the cast doesn't work and this must be specialized.
+/// The default is @c std::hash but that can be overridden by specializing this method.
 template <typename E>
-uintmax_t
+size_t
 Lexicon_Hash(E e) {
-  return static_cast<uintmax_t>(e);
+  static constexpr std::hash<E> hasher;
+  return hasher(e);
 }
 
 /** A bidirectional mapping between names and enumeration values.
@@ -77,6 +77,15 @@
   struct Item;
 
 public:
+  /// An association of an enumeration value and a name.
+  /// @ note Used for initializer lists that have just a primary value.
+  using Pair = std::tuple<E, std::string_view>;
+
+  /// Index in @c Pair for the enumeration value.
+  static constexpr auto VALUE_IDX = 0;
+  /// Index in @c Pair for name.
+  static constexpr auto NAME_IDX = 1;
+
   /** 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.
@@ -102,9 +111,6 @@
    */
   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.
@@ -124,7 +130,7 @@
    * 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
+   * The default handlers are optional can 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.
@@ -140,7 +146,7 @@
    *
    * 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
+   * The default handlers are optional can 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.
@@ -153,7 +159,7 @@
    * @param handler_1 A default handler.
    * @param handler_2 A default handler.
    *
-   * @a handler_2 is optional can be be omitted. The argument values are the same as for
+   * @a handler_2 is optional can be omitted. The argument values are the same as for
    * @c set_default.
    *
    * @see set_default.
@@ -167,7 +173,7 @@
    * @param value Value to look up.
    * @return The name for @a value.
    */
-  std::string_view operator[](E value) const;
+  std::string_view operator[](E const& value) const;
 
   /** Get the value for a @a name.
    *
@@ -229,43 +235,59 @@
   /// Get the number of values with definitions.
   size_t count() const;
 
-  /** Iterator over pairs of values and primary name pairs.
-   * Value is a 2-tuple of the enumeration type and the primary name.
-   */
-  class const_iterator {
-    using self_type = const_iterator;
-
+protected:
+  /// Common features of container iterators.
+  class base_iterator {
+    using self_type = base_iterator;
   public:
-    using value_type        = const Pair;
-    using pointer           = value_type *;
-    using reference         = value_type &;
-    using difference_type   = ptrdiff_t;
-    using iterator_category = std::bidirectional_iterator_tag;
-
-    /// Default constructor.
-    const_iterator() = default;
-
-    /// Copy constructor.
-    const_iterator(self_type const &that) = default;
-
-    /// Move construcgtor.
-    const_iterator(self_type &&that) = default;
-
+    using value_type        = const Pair; ///< Iteration value.
+    using pointer           = value_type *; ///< Pointer to iteration value.
+    using reference         = value_type &; ///< Reference to iteration value.
+    using difference_type   = ptrdiff_t; ///< Type of difference between iterators.
+    using iterator_category = std::bidirectional_iterator_tag; ///< Concepts for iterator.
+    /// Default constructor (invalid iterator)
+    base_iterator() = default;
     /// Dereference.
     reference operator*() const;
-
     /// Dereference.
     pointer operator->() const;
+    /// Equality.
+    bool operator==(self_type const &that) const;
+    /// Inequality.
+    bool operator!=(self_type const &that) const;
+
+  protected:
+    base_iterator(Item const * item) : _item(item) {}
+
+    const Item *_item{nullptr};                      ///< Current location in the container.
+  };
+
+public:
+
+  /** Iterator over pairs of values and primary name pairs.
+   *  The value type is a @c Pair with the value and name.
+   */
+  class value_iterator : public base_iterator {
+    using super_type = base_iterator;
+    using self_type = value_iterator;
+
+  public:
+    using value_type        = typename super_type::value_type;
+    using pointer           = typename super_type::pointer;
+    using reference         = typename super_type::reference;
+
+    /// Default constructor.
+    value_iterator() = default;
+
+    /// Copy constructor.
+    value_iterator(self_type const &that) = default;
+
+    /// Move constructor.
+    value_iterator(self_type &&that) = default;
 
     /// Assignment.
     self_type &operator=(self_type const &that) = default;
 
-    /// Equality.
-    bool operator==(self_type const &that) const;
-
-    /// Inequality.
-    bool operator!=(self_type const &that) const;
-
     /// Increment.
     self_type &operator++();
 
@@ -279,18 +301,50 @@
     self_type operator--(int);
 
   protected:
-    const_iterator(const Item *item); ///< Internal constructor.
-
-    /// Update the internal values after changing the iterator location.
-    void update();
-
-    const Item *_item{nullptr};                      ///< Current location in the container.
-    typename std::remove_const<value_type>::type _v; ///< Synthesized value for dereference.
+    value_iterator(const Item *item) : super_type(item) {}; ///< Internal constructor.
 
     friend Lexicon;
   };
 
-  // Only constant iterator allowed, values cannot be modified.
+  class name_iterator : public base_iterator {
+  private:
+    using self_type = name_iterator;
+    using super_type = base_iterator;
+  public:
+    /// Default constructor.
+    name_iterator() = default;
+
+    /// Copy constructor.
+    name_iterator(self_type const &that) = default;
+
+    /// Move constructor.
+    name_iterator(self_type &&that) = default;
+
+    /// Assignment.
+    self_type &operator=(self_type const &that) = default;
+
+    /// Increment.
+    self_type &operator++();
+
+    /// Increment.
+    self_type operator++(int);
+
+    /// Decrement.
+    self_type &operator--();
+
+    /// Decrement.
+    self_type operator--(int);
+
+  protected:
+    name_iterator(const Item *item) : super_type(item) {}; ///< Internal constructor.
+
+    friend Lexicon;
+  };
+
+  /// Iterator over values (each with a primary name).
+  using const_iterator = value_iterator;
+  /// Iterator over values.
+  /// @note All iteration is over constant pairs, no modification is possible.
   using iterator = const_iterator;
 
   /// Iteration begin.
@@ -299,6 +353,34 @@
   /// Iteration end.
   const_iterator end() const;
 
+  /// Iteration over names - every value/name pair.
+  name_iterator begin_names() const { return { _by_name.begin() }; }
+  /// Iteration over names - every value/name pair.
+  name_iterator end_names() const { return { _by_name.end() }; }
+
+  /// @cond INTERNAL
+  // Helper struct to return to enable container iteration for names.
+  struct ByNameHelper {
+    self_type const & _lexicon;
+    ByNameHelper(self_type const & self) : _lexicon(self) {}
+    name_iterator begin() const { return _lexicon.begin_names(); }
+    name_iterator end() const { return _lexicon.end_names(); }
+  };
+  /// @endcond
+
+  /** Enable container iteration by name.
+   * The return value is a tempoary of indeterminate type that provides @c begin and @c end methods which
+   * return name based iterators for @a this. This is useful for container based iteration. E.g. to iterate
+   * over all of the value/name pairs,
+   * @code
+   * for ( auto const & pair : lexicon.by_names()) {
+   *   // code
+   * }
+   * @endcode
+   * @return Temporary.
+   */
+  ByNameHelper by_names() const { return { *this }; }
+
 protected:
   /// Handle providing a default name.
   using NameDefault = std::variant<std::monostate, std::string_view, UnknownValueHandler>;
@@ -307,18 +389,21 @@
 
   /// Visitor functor for handling @c NameDefault.
   struct NameDefaultVisitor {
-    E _value;
+    E _value; ///< Value to use for default.
 
+    /// Visitor - invalid value type.
     std::string_view
     operator()(std::monostate const &) const {
-      throw std::domain_error(detail::what("Lexicon: invalid enumeration value {}", static_cast<int>(_value)).data());
+      throw std::domain_error("Lexicon: invalid enumeration value");
     }
 
+    /// Visitor - literal string.
     std::string_view
     operator()(std::string_view const &name) const {
       return name;
     }
 
+    /// Visitor - string generator.
     std::string_view
     operator()(UnknownValueHandler const &handler) const {
       return handler(_value);
@@ -327,18 +412,21 @@
 
   /// Visitor functor for handling @c ValueDefault.
   struct ValueDefaultVisitor {
-    std::string_view _name;
+    std::string_view _name; ///< Name of visited pair.
 
+    /// Vistor - invalid value.
     E
     operator()(std::monostate const &) const {
       throw std::domain_error(detail::what("Lexicon: Unknown name \"{}\"", _name).data());
     }
 
+    /// Visitor - value.
     E
     operator()(E const &value) const {
       return value;
     }
 
+    /// Visitor - value generator.
     E
     operator()(UnknownNameHandler const &handler) const {
       return handler(_name);
@@ -356,40 +444,33 @@
      */
     Item(E value, std::string_view name);
 
-    E _value;               ///< Definition value.
-    std::string_view _name; ///< Definition name
+    Pair _payload; ///< Enumeration and name.
 
-    /// Intrusive linkage for name lookup.
+    /// @cond INTERNAL_DETAIL
+    // Intrusive list linkage support.
     struct NameLinkage {
       Item *_next{nullptr};
       Item *_prev{nullptr};
 
       static Item *&next_ptr(Item *);
-
       static Item *&prev_ptr(Item *);
-
       static std::string_view key_of(Item *);
-
       static uint32_t hash_of(std::string_view s);
-
       static bool equal(std::string_view const &lhs, std::string_view const &rhs);
     } _name_link;
 
-    /// Intrusive linkage for value lookup.
+    // Intrusive linkage for value lookup.
     struct ValueLinkage {
       Item *_next{nullptr};
       Item *_prev{nullptr};
 
       static Item *&next_ptr(Item *);
-
       static Item *&prev_ptr(Item *);
-
       static E key_of(Item *);
-
-      static uintmax_t hash_of(E);
-
+      static size_t hash_of(E);
       static bool equal(E lhs, E rhs);
     } _value_link;
+    /// @endcond
   };
 
   /// Copy @a name in to local storage.
@@ -411,8 +492,9 @@
 // ----
 // Item
 
-template <typename E> Lexicon<E>::Item::Item(E value, std::string_view name) : _value(value), _name(name) {}
+template <typename E> Lexicon<E>::Item::Item(E value, std::string_view name) : _payload(value, name) {}
 
+/// @cond INTERNAL_DETAIL
 template <typename E>
 auto
 Lexicon<E>::Item::NameLinkage::next_ptr(Item *item) -> Item *& {
@@ -440,13 +522,13 @@
 template <typename E>
 std::string_view
 Lexicon<E>::Item::NameLinkage::key_of(Item *item) {
-  return item->_name;
+  return std::get<NAME_IDX>(item->_payload);
 }
 
 template <typename E>
 E
 Lexicon<E>::Item::ValueLinkage::key_of(Item *item) {
-  return item->_value;
+  return std::get<VALUE_IDX>(item->_payload);
 }
 
 template <typename E>
@@ -456,9 +538,8 @@
 }
 
 template <typename E>
-uintmax_t
+size_t
 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 Lexicon_Hash<E>(value);
 }
 
@@ -473,6 +554,7 @@
 Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs) {
   return lhs == rhs;
 }
+/// @endcond
 
 // -------
 // Lexicon
@@ -517,10 +599,9 @@
 
 template <typename E>
 std::string_view
-Lexicon<E>::operator[](E value) const {
-  auto spot = _by_value.find(value);
-  if (spot != _by_value.end()) {
-    return spot->_name;
+Lexicon<E>::operator[](E const& value) const {
+  if ( auto spot = _by_value.find(value) ; spot != _by_value.end()) {
+    return std::get<NAME_IDX>(spot->_payload);
   }
   return std::visit(NameDefaultVisitor{value}, _name_default);
 }
@@ -528,9 +609,8 @@
 template <typename E>
 E
 Lexicon<E>::operator[](std::string_view const &name) const {
-  auto spot = _by_name.find(name);
-  if (spot != _by_name.end()) {
-    return spot->_value;
+  if ( auto spot = _by_name.find(name) ; spot != _by_name.end()) {
+    return std::get<VALUE_IDX>(spot->_payload);
   }
   return std::visit(ValueDefaultVisitor{name}, _value_default);
 }
@@ -541,7 +621,7 @@
   if (names.size() < 1) {
     throw std::invalid_argument("A defined value must have at least a primary name");
   }
-  for (auto name : names) {
+  for (auto const& name : names) {
     if (_by_name.find(name) != _by_name.end()) {
       throw std::invalid_argument(detail::what("Duplicate name '{}' in Lexicon", name));
     }
@@ -566,7 +646,7 @@
 template <typename E>
 auto
 Lexicon<E>::define(const Pair &pair) -> self_type & {
-  return this->define(std::get<0>(pair), {std::get<1>(pair)});
+  return this->define(std::get<VALUE_IDX>(pair), {std::get<NAME_IDX>(pair)});
 }
 
 template <typename E>
@@ -618,54 +698,39 @@
 // Iterators
 
 template <typename E>
-void
-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) {
-    this->update();
-  };
+auto
+Lexicon<E>::base_iterator::operator*() const -> reference {
+  return _item->_payload;
 }
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator*() const -> reference {
-  return _v;
-}
-
-template <typename E>
-auto
-Lexicon<E>::const_iterator::operator->() const -> pointer {
-  return &_v;
+Lexicon<E>::base_iterator::operator->() const -> pointer {
+  return &(_item->_payload);
 }
 
 template <typename E>
 bool
-Lexicon<E>::const_iterator::operator==(self_type const &that) const {
+Lexicon<E>::base_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>::base_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)) {
-    this->update();
-  }
+Lexicon<E>::value_iterator::operator++() -> self_type & {
+  super_type::_item = super_type::_item->_value_link._next;
   return *this;
 }
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator++(int) -> self_type {
+Lexicon<E>::value_iterator::operator++(int) -> self_type {
   self_type tmp{*this};
   ++*this;
   return tmp;
@@ -673,16 +738,44 @@
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator--() -> self_type & {
-  if (nullptr != (_item = _item->_value_link->_prev)) {
-    this->update();
-  }
+Lexicon<E>::value_iterator::operator--() -> self_type & {
+  super_type::_item = super_type::_item->_value_link->_prev;
   return *this;
 }
 
 template <typename E>
 auto
-Lexicon<E>::const_iterator::operator--(int) -> self_type {
+Lexicon<E>::value_iterator::operator--(int) -> self_type {
+  self_type tmp;
+  ++*this;
+  return tmp;
+}
+
+template <typename E>
+auto
+Lexicon<E>::name_iterator::operator++() -> self_type & {
+  super_type::_item = super_type::_item->_name_link._next;
+  return *this;
+}
+
+template <typename E>
+auto
+Lexicon<E>::name_iterator::operator++(int) -> self_type {
+  self_type tmp{*this};
+  ++*this;
+  return tmp;
+}
+
+template <typename E>
+auto
+Lexicon<E>::name_iterator::operator--() -> self_type & {
+  super_type::_item = super_type::_item->_name_link->_prev;
+  return *this;
+}
+
+template <typename E>
+auto
+Lexicon<E>::name_iterator::operator--(int) -> self_type {
   self_type tmp;
   ++*this;
   return tmp;
diff --git a/lib/swoc/include/swoc/MemArena.h b/lib/swoc/include/swoc/MemArena.h
index a68e9be..a4dc409 100644
--- a/lib/swoc/include/swoc/MemArena.h
+++ b/lib/swoc/include/swoc/MemArena.h
@@ -36,7 +36,7 @@
   using self_type = MemArena; ///< Self reference type.
 
 public:
-  static constexpr size_t DEFAULT_ALIGNMENT{1};
+  static constexpr size_t DEFAULT_ALIGNMENT{1}; ///< Default memory alignment.
 
   /// Simple internal arena block of memory. Maintains the underlying memory.
   struct Block {
@@ -98,7 +98,7 @@
     bool is_full() const;
 
   protected:
-    friend MemArena;
+    friend MemArena; ///< Container.
 
     /** Override @c operator @c delete.
      *
@@ -139,15 +139,18 @@
     size_t allocated{0}; ///< Current allocated (in use) bytes.
 
     struct Linkage {
+      /// @cond INTERNAL_DETAIL
       Block *_next{nullptr};
       Block *_prev{nullptr};
 
       static Block *&next_ptr(Block *);
 
       static Block *&prev_ptr(Block *);
-    } _link;
+      /// @endcond
+    } _link; ///< Intrusive list support.
   };
 
+  /// Intrusive list of blocks.
   using BlockList = IntrusiveDList<Block::Linkage>;
 
   /** Construct with reservation hint.
@@ -184,9 +187,11 @@
   /// Destructor.
   ~MemArena();
 
-  self_type &operator=(self_type const &that) = delete;
+  /// No copy assignment.
+  self_type & operator=(self_type const & that) = delete;
 
-  self_type &operator=(self_type &&that);
+  /// Move assignment.
+  self_type & operator=(self_type &&that);
 
   /** Make a self-contained instance.
    *
@@ -309,9 +314,30 @@
   /// @return The amount of free space.
   size_t remaining() const;
 
+  /** Get aligned and sized remnant.
+   *
+   * @tparam T Element type.
+   * @param n Number of instances of @a T
+   * @return A span that is in the remnant, correctly aligned with minimal padding.
+   *
+   * This is guaranteed to be the same bytes as if @c alloc<T> was called. The returned span will
+   * always be the specified size, the remnant will be expanded as needed.
+   */
+  template <typename T> MemSpan<T> remnant_span(size_t n);
+
   /// @return Contiguous free space in the current internal block.
   MemSpan<void> remnant();
 
+  /** Get an aligned remnant.
+   *
+   * @param n Remnant size.
+   * @param align Memory alignment (default 1, must be power of 2).
+   * @return Space in the remnant with minimal alignment padding.
+   *
+   * @note This will always return a span of @a n bytes, the remnant will be expanded as needed.
+   */
+  MemSpan<void> remnant(size_t n, size_t align = DEFAULT_ALIGNMENT);
+
   /** Require @a n bytes of contiguous memory to be available for allocation.
    *
    * @param n Number of bytes.
@@ -338,19 +364,20 @@
    */
   size_t reserved_size() const;
 
-  using const_iterator = BlockList::const_iterator;
-  using iterator       = const_iterator; // only const iteration allowed on blocks.
+  using const_iterator = BlockList::const_iterator; ///< Constant element iteration.
+  using iterator       = const_iterator; ///< Element iteration.
 
-  /// Iterate over active blocks.
+  /// First active block.
   const_iterator begin() const;
 
+  /// After Last active block.
   const_iterator end() const;
 
-  /// Iterator over frozen blocks.
+  /// First frozen block.
   const_iterator frozen_begin() const;
 
+  /// After last frozen block.
   const_iterator frozen_end() const;
-
 protected:
   /** Internally allocates a new block of memory of size @a n bytes.
    *
@@ -454,8 +481,13 @@
 
   /// Drop all items in the free list.
   void clear();
+
+  /// Access the wrapped arena directly.
+  MemArena & arena();
 };
-// Implementation
+
+// --- Implementation ---
+/// @cond INTERNAL_DETAIL
 
 inline auto
 MemArena::Block::Linkage::next_ptr(Block *b) -> Block *& {
@@ -508,20 +540,6 @@
   return zret;
 }
 
-template <typename T>
-MemSpan<T>
-MemArena::alloc_span(size_t n) {
-  return this->alloc(sizeof(T) * n, size_t{alignof(T)}).rebind<T>();
-}
-
-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() {
   return {this->data() + allocated, this->remaining()};
@@ -544,10 +562,40 @@
 
 inline size_t
 MemArena::Block::align_padding(void const *ptr, size_t align) {
-  auto delta = uintptr_t(ptr) & (size_t(align) - 1);
-  return delta ? size_t(align) - delta : delta;
+  if (auto delta = uintptr_t(ptr) & (align - 1) ; delta > 0) {
+    return align - delta;
+  }
+  return 0;
 }
 
+inline MemArena::MemArena(size_t n) : _reserve_hint(n) {}
+
+template <typename T>
+MemSpan<T>
+MemArena::alloc_span(size_t n) {
+  return this->alloc(sizeof(T) * n, alignof(T)).rebind<T>();
+}
+
+template <typename T, typename... Args>
+T *
+MemArena::make(Args &&... args) {
+  return new (this->alloc(sizeof(T), alignof(T)).data()) T(std::forward<Args>(args)...);
+}
+
+template <typename T>
+MemSpan<T>
+MemArena::remnant_span(size_t n) {
+  auto span = this->require(sizeof(T) * n, alignof(T)).remnant();
+  return span.remove_prefix(Block::align_padding(span.data(), alignof(T))).rebind<T>();
+}
+
+template <>
+inline MemSpan<void>
+MemArena::remnant_span<void>(size_t n) { return this->require(n).remnant().prefix(n); }
+
+inline MemSpan<void>
+MemArena::remnant(size_t n, size_t align) { return this->require(n, align).remnant().prefix(n); }
+
 inline size_t
 MemArena::size() const {
   return _active_allocated;
@@ -626,4 +674,8 @@
   _list._next = nullptr;
 }
 
+template < typename T > MemArena & FixedArena<T>::arena() { return _arena; }
+
+/// @endcond INTERNAL_DETAIL
+
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/MemSpan.h b/lib/swoc/include/swoc/MemSpan.h
index be1e6bf..726211e 100644
--- a/lib/swoc/include/swoc/MemSpan.h
+++ b/lib/swoc/include/swoc/MemSpan.h
@@ -21,6 +21,7 @@
 #include <exception>
 
 #include "swoc/swoc_version.h"
+#include "swoc/Scalar.h"
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 /** A span of contiguous piece of memory.
@@ -53,9 +54,9 @@
   size_t _count = 0;       ///< Number of elements.
 
 public:
-  using value_type     = T;
-  using iterator       = T *;
-  using const_iterator = T const *;
+  using value_type     = T; ///< Element type for span.
+  using iterator       = T *; ///< Iterator.
+  using const_iterator = T const *; ///< Constant iterator.
 
   /// Default constructor (empty buffer).
   constexpr MemSpan() = default;
@@ -82,7 +83,7 @@
    * @tparam N Number of elements in the array.
    * @param a The array.
    */
-  template <auto N> MemSpan(T (&a)[N]);
+  template <auto N> constexpr MemSpan(T (&a)[N]);
 
   /** Construct from constant @c std::array.
    *
@@ -136,7 +137,7 @@
       @return @c true if the contents of @a that are the same as the content of @a this,
       @c false otherwise.
    */
-  bool operator==(self_type const &that) const;
+  constexpr bool operator==(self_type const &that) const;
 
   /** Identical.
 
@@ -149,7 +150,7 @@
       @return @c true if @a that does not refer to the same span as @a this,
       @c false otherwise.
    */
-  bool operator!=(self_type const &that) const;
+  constexpr bool operator!=(self_type const &that) const;
 
   /// Assignment - the span is copied, not the content.
   self_type &operator=(self_type const &that) = default;
@@ -167,24 +168,27 @@
 
   /// Check for empty span (no content).
   /// @see operator bool
-  bool empty() const;
+  constexpr bool empty() const;
 
   /// @name Accessors.
   //@{
   /// Pointer to the first element in the span.
-  T *begin() const;
+  constexpr T *begin() const;
 
   /// Pointer to first element not in the span.
-  T *end() const;
+  constexpr T *end() const;
 
   /// Number of elements in the span
-  size_t count() const;
+  constexpr size_t count() const;
 
   /// Number of bytes in the span.
   size_t size() const;
 
-  /// Pointer to memory in the span.
-  T *data() const;
+  /// @return Pointer to memory in the span.
+  T * data() const;
+
+  /// @return Pointer to immediate after memory in the span.
+  constexpr T *data_end() const;
 
   /** Access the first element in the span.
    *
@@ -404,9 +408,9 @@
   size_t size() const;
 
   /// Pointer to memory in the span.
-  value_type *data() const;
+  constexpr value_type *data() const;
 
-  /// Pointer to memory in the span.
+  /// Pointer to just after memory in the span.
   value_type *data_end() const;
 
   /** Create a new span for a different type @a V on the same memory.
@@ -478,6 +482,26 @@
    */
   constexpr self_type subspan(size_t offset, size_t count) const;
 
+  /** Align span for a type.
+   *
+   * @tparam T Alignment type.
+   * @return A suffix of the span suitably aligned for @a T.
+   *
+   * The minimum amount of space is removed from the front to yield an aligned span. If the span is not large
+   * enough to perform the alignment, the pointer is aligned and the size reduced to zero (empty).
+   */
+  template <typename T> self_type align() const;
+
+  /** Force memory alignment.
+   *
+   * @param n Alignment size (must be power of 2).
+   * @return An aligned span.
+   *
+   * The minimum amount of space is removed from the front to yield an aligned span. If the span is not large
+   * enough to perform the alignment, the pointer is aligned and the size reduced to zero (empty).
+   */
+  self_type align(size_t n) const;
+
   /** Return a view of the memory.
    *
    * @return A @c string_view covering the span contents.
@@ -488,8 +512,7 @@
 // -- Implementation --
 
 namespace detail {
-/// pointer distance calculations for all types, @b including @c <void*>.
-/// This is useful in templates.
+/// @cond INTERNAL_DETAIL
 inline size_t
 ptr_distance(void const *first, void const *last) {
   return static_cast<const char *>(last) - static_cast<const char *>(first);
@@ -505,6 +528,7 @@
 ptr_add(void *ptr, size_t count) {
   return static_cast<char *>(ptr) + count;
 }
+/// @endcond
 
 /** Meta Function to check the type compatibility of two spans..
  *
@@ -616,35 +640,48 @@
 }
 
 using std::memcpy;
-using std::memcpy;
 
+/** Set contents of a span to a fixed @a value.
+ *
+ * @tparam T Span type.
+ * @param dst Span to change.
+ * @param value Source value.
+ * @return
+ */
 template <typename T>
 inline MemSpan<T> const &
-memset(MemSpan<T> const &dst, T const &t) {
+memset(MemSpan<T> const &dst, T const &value) {
   for (auto &e : dst) {
-    e = t;
+    e = value;
   }
   return dst;
 }
 
+/// @cond INTERNAL_DETAIL
+
+// Optimization for @c char.
 inline MemSpan<char> const &
 memset(MemSpan<char> const &dst, char c) {
   std::memset(dst.data(), c, dst.size());
   return dst;
 }
 
+// Optimization for @c unsigned @c char
 inline MemSpan<unsigned char> const &
 memset(MemSpan<unsigned char> const &dst, unsigned char c) {
   std::memset(dst.data(), c, dst.size());
   return dst;
 }
 
+// Optimization for @c char.
 inline MemSpan<void> const &
 memset(MemSpan<void> const &dst, char c) {
   std::memset(dst.data(), c, dst.size());
   return dst;
 }
 
+/// @endcond
+
 using std::memset;
 
 // --- MemSpan<T> ---
@@ -653,7 +690,7 @@
 
 template <typename T> constexpr MemSpan<T>::MemSpan(T *first, T *last) : _ptr{first}, _count{detail::ptr_distance(first, last)} {}
 
-template <typename T> template <auto N> MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{a}, _count{N} {}
+template <typename T> template <auto N> constexpr MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{a}, _count{N} {}
 
 template <typename T> constexpr MemSpan<T>::MemSpan(std::nullptr_t) {}
 
@@ -691,13 +728,13 @@
 }
 
 template <typename T>
-bool
+constexpr bool
 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
+constexpr bool
 MemSpan<T>::operator!=(self_type const &that) const {
   return !(*this == that);
 }
@@ -712,13 +749,13 @@
   return _count != 0;
 }
 
-template <typename T>
+template <typename T> constexpr
 bool
 MemSpan<T>::empty() const {
   return _count == 0;
 }
 
-template <typename T>
+template <typename T> constexpr
 T *
 MemSpan<T>::begin() const {
   return _ptr;
@@ -730,7 +767,13 @@
   return _ptr;
 }
 
-template <typename T>
+template <typename T> constexpr
+T *
+MemSpan<T>::data_end() const {
+  return _ptr + _count;
+}
+
+template <typename T> constexpr
 T *
 MemSpan<T>::end() const {
   return _ptr + _count;
@@ -742,7 +785,7 @@
   return _ptr[idx];
 }
 
-template <typename T>
+template <typename T> constexpr
 size_t
 MemSpan<T>::count() const {
   return _count;
@@ -760,14 +803,14 @@
   return _ptr <= ptr && ptr < _ptr + _count;
 }
 
-template <typename T>
-constexpr auto
+template <typename T> constexpr
+auto
 MemSpan<T>::prefix(size_t count) const -> self_type {
   return {_ptr, std::min(count, _count)};
 }
 
-template <typename T>
-constexpr auto
+template <typename T> constexpr
+auto
 MemSpan<T>::first(size_t count) const -> self_type {
   return this->prefix(count);
 }
@@ -781,8 +824,8 @@
   return *this;
 }
 
-template <typename T>
-constexpr auto
+template <typename T> constexpr
+auto
 MemSpan<T>::suffix(size_t count) const -> self_type {
   count = std::min(_count, count);
   return {(_ptr + _count) - count, count};
@@ -821,7 +864,7 @@
 
 template <typename T>
 template <typename F>
-MemSpan<T> &
+typename MemSpan<T>::self_type &
 MemSpan<T>::apply(F &&f) {
   for (auto &item : *this) {
     f(item);
@@ -905,7 +948,7 @@
   return _size == 0;
 }
 
-inline void *
+inline constexpr void *
 MemSpan<void>::data() const {
   return _ptr;
 }
@@ -963,6 +1006,17 @@
   return *this;
 }
 
+template <typename T>
+MemSpan<void>::self_type
+MemSpan<void>::align() const { return this->align(alignof(T)); }
+
+inline MemSpan<void>::self_type
+MemSpan<void>::align(size_t n) const {
+  auto p = uintptr_t(_ptr);
+  auto padding = p & (n - 1);
+  return { reinterpret_cast<void*>(p + padding), _size - std::min<uintptr_t>(_size, padding) };
+}
+
 template <typename U>
 MemSpan<U>
 MemSpan<void>::rebind() const {
diff --git a/lib/swoc/include/swoc/RBTree.h b/lib/swoc/include/swoc/RBTree.h
index f391e2d..2594e6f 100644
--- a/lib/swoc/include/swoc/RBTree.h
+++ b/lib/swoc/include/swoc/RBTree.h
@@ -59,6 +59,7 @@
   /// @return The color of the node.
   Color color() const;
 
+  /// @return The left most descendant of @a this, or @a null ptr if no left child.
   self_type *left_most_descendant() const;
 
   /** Reverse a direction
@@ -182,11 +183,12 @@
 
   /// Support for @c IntrusiveDList
   struct Linkage {
+    /// @return Reference to internal pointer to the next element.
     static self_type *&
     next_ptr(self_type *t) {
       return swoc::ptr_ref_cast<self_type>(t->_next);
     }
-
+    /// @return Reference to the internal pointer to the previoius element.
     static self_type *&
     prev_ptr(self_type *t) {
       return swoc::ptr_ref_cast<self_type>(t->_prev);
diff --git a/lib/swoc/include/swoc/Scalar.h b/lib/swoc/include/swoc/Scalar.h
index 5191eef..640e6b9 100644
--- a/lib/swoc/include/swoc/Scalar.h
+++ b/lib/swoc/include/swoc/Scalar.h
@@ -195,7 +195,7 @@
   /// @cond INTERNAL_DETAIL
   // Assignment from internal rounding structures.
   // Conversion constructor.
-  constexpr Scalar(detail::scalar_round_up_t<N, C, T> const &that);
+  constexpr Scalar(detail::scalar_round_up_t<N, C, T> const &v);
 
   // Conversion constructor.
   constexpr Scalar(detail::scalar_round_down_t<N, C, T> const &that);
@@ -240,7 +240,7 @@
    */
   template <typename I> self_type &operator=(detail::scalar_unit_round_down_t<I> n);
 
-  /** Internal method to assign a differently scaled SCALR to be rounded up.
+  /** Internal method to assign a differently scaled SCALAR to be rounded up.
    *
    * @param v The embedding of the value to be scaled.
    * @return @a this
@@ -250,7 +250,7 @@
    */
   self_type &operator=(detail::scalar_round_up_t<N, C, T> v);
 
-  /** Internal method to assign a differently scaled SCALR to be rounded down.
+  /** Internal method to assign a differently scaled SCALAR to be rounded down.
    *
    * @param v The embedding of the value to be scaled.
    * @return @a this
@@ -303,6 +303,15 @@
   /// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
   self_type &operator+=(self_type const &that);
 
+  /** Cross scale addition.
+   *
+   * @tparam S Source scale.
+   * @tparam I Source raw type.
+   * @param that Value.
+   * @return @a this
+   *
+   * @a that is scaled as needed to be added to @a this.
+   */
   template <intmax_t S, typename I> self_type &operator+=(Scalar<S, I, T> const &that);
 
   /// @cond INTERNAL_DETAIL
@@ -339,6 +348,15 @@
   /// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
   self_type &operator-=(self_type const &that);
 
+  /** Subtraction.
+   *
+   * @tparam S Scale.
+   * @tparam I Raw type.
+   * @param that Value to subtract.
+   * @return @a this
+   *
+   * The value of @a that is subtracted from the value of @a this.
+   */
   template <intmax_t S, typename I> self_type &operator-=(Scalar<S, I, T> const &that);
 
   /// @cond INTERNAL_DETAIL
@@ -373,6 +391,8 @@
   Counter _n; ///< Number of scale units.
 };
 
+/// @cond Scalar_INTERNAL
+// Avoid issues with doxygen matching externally defined methods.
 template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar() : _n() {}
 
 template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(Counter n) : _n(n) {}
@@ -416,7 +436,7 @@
   return _n * SCALE;
 }
 
-template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::operator C() const {
+template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::operator Counter() const {
   return _n * SCALE;
 }
 
@@ -436,7 +456,7 @@
 
 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;
 }
@@ -520,6 +540,8 @@
   return SCALE;
 }
 
+/// @endcond Scalar_INTERNAL
+
 // --- Functions ---
 
 /** Prepare units @a n to be assigned to a @c Scalar, rounding up as needed.
@@ -629,6 +651,7 @@
   return *this;
 }
 
+/// @cond INTERNAL_DETAIL
 template <intmax_t N, typename C, typename T>
 template <typename I>
 auto
@@ -659,18 +682,25 @@
   return *this;
 }
 
+/// @endcond
+
 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 {
   return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) += rhs;
 }
 
+/** Add a two scalars of the same type.
+ * @return A scalar of the same type holding the sum.
+ */
 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) {
   return Scalar<N, C, T>(lhs) += rhs;
 }
 
+/// @cond INTERNAL_DETAIL
+// These handle adding a wrapper and a scalar.
 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) {
@@ -718,6 +748,7 @@
 operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs) {
   return Scalar<N, C, T>(lhs) += rhs._n;
 }
+/// @endcond
 
 template <intmax_t N, typename C, typename T>
 auto
@@ -736,6 +767,8 @@
   return *this;
 }
 
+/// @cond INTERNAL_DETAIL
+
 template <intmax_t N, typename C, typename T>
 template <typename I>
 auto
@@ -766,18 +799,25 @@
   return *this;
 }
 
+/// @endcond
+
 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 {
   return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) -= rhs;
 }
 
+/** Subtract scalars.
+ * @return A scalar of the same type holding the difference @a lhs - @a 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) {
   return Scalar<N, C, T>(lhs) -= rhs;
 }
 
+/// @cond INTERNAL_DETAIL
+// Handle subtraction for intermediate wrappers.
 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) {
@@ -825,6 +865,7 @@
 operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs) {
   return Scalar<N, C, T>(lhs) -= rhs._n;
 }
+/// @endcond
 
 template <intmax_t N, typename C, typename T>
 auto
diff --git a/lib/swoc/include/swoc/TextView.h b/lib/swoc/include/swoc/TextView.h
index 8f9d1e0..e153741 100644
--- a/lib/swoc/include/swoc/TextView.h
+++ b/lib/swoc/include/swoc/TextView.h
@@ -20,7 +20,7 @@
 #include <limits>
 
 #include "swoc/swoc_version.h"
-#include "tscpp/util/string_view_util.h"
+#include "swoc/string_view_util.h"
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 
@@ -43,9 +43,10 @@
     - self / parent type
     - @c std::string
     - literal string
-    - C-string
+    - C-string pointer
     - pointer and count
     - begin/end style pointers.
+    - character containers that have the STL standard @c size and @c data methods.
  */
 class TextView : public std::string_view {
   using self_type  = TextView;         ///< Self reference type.
@@ -111,17 +112,21 @@
    *
    * @param src A pointer to a C-string.
    *
-   * @internal This is a reference because it is otherwise ambiguous with the array constructor.
+   * The view does not include the terminating nul.
+   *
+   * @internal @a src a reference because it is otherwise ambiguous with the literal constructor.
    */
-  TextView(char * & src) : super_type(src) {}
+  TextView(char * & src) : super_type(src, src ? strlen(src) : 0) {}
 
   /** Construct from a const C-string.
    *
    * @param src Pointer to a const C-string.
    *
-   * @internal This is a reference because it is otherwise ambiguous with the array constructor.
+   * The view does not include the terminating nul.
+   *
+   * @internal @a src a reference because it is otherwise ambiguous with the literal constructor.
    */
-  TextView(char const * & src) : super_type(src) {}
+  TextView(char const * & src) : super_type(src, src ? strlen(src) : 0) {}
 
   /** Construct from nullptr.
       This implicitly makes the length 0.
@@ -143,7 +148,7 @@
   /// Assign from C-string @a s.
   self_type &operator=(char *&s);
   /// Assign from C-string @a s.
-  self_type &operator=(char const *&s);
+  self_type &operator=(char const * & s);
 
   /// Assign from a @c std::string.
   self_type &operator=(const std::string &s);
@@ -510,7 +515,7 @@
    * @return The prefix bounded by the first character satisfying @a pred, or all of @a this if none
    * is found.
    *
-   * The prefix is removed and returned if a a character satisfying @a pred is found, otherwise
+   * The prefix is removed and returned if a character satisfying @a pred is found, otherwise
    * all of @a this is removed and returned.
    *
    * @note The matching character is discarded if found.
@@ -962,6 +967,8 @@
 // definition and the reference documentation is messed up. Sigh.
 
 // === TextView Implementation ===
+/// @cond TextView_INTERNAL
+// Doxygen doesn't match these up well due to various type and template issues.
 inline constexpr TextView::TextView(const char *ptr, size_t n) noexcept
   : super_type(ptr, n == npos ? (ptr ? ::strlen(ptr) : 0) : n) {}
 inline constexpr TextView::TextView(char const *first, char const *last) noexcept : super_type(first, last - first) {}
@@ -978,8 +985,8 @@
     set[static_cast<uint8_t>(c)] = true;
 }
 
-inline TextView &
-TextView::clear() {
+inline auto
+TextView::clear() -> self_type & {
   new (this) self_type();
   return *this;
 }
@@ -998,21 +1005,21 @@
   return !this->empty();
 }
 
-inline TextView &
-TextView::operator++() {
+inline auto
+TextView::operator++() -> self_type & {
   this->remove_prefix(1);
   return *this;
 }
 
-inline TextView
-TextView::operator++(int) {
+inline auto
+TextView::operator++(int) -> self_type {
   self_type zret{*this};
   this->remove_prefix(1);
   return zret;
 }
 
-inline TextView &
-TextView::operator+=(size_t n) {
+inline auto
+TextView::operator+=(size_t n) -> self_type &{
   this->remove_prefix(n);
   return *this;
 }
@@ -1023,42 +1030,42 @@
   return *this = self_type{s, s[N - 1] ? N : N - 1};
 }
 
-inline TextView &
-TextView::operator=(super_type const &that) {
+inline auto
+TextView::operator=(super_type const &that) -> self_type & {
   this->super_type::operator=(that);
   return *this;
 }
 
-inline TextView &
-TextView::operator=(char *&s) {
+inline auto
+TextView::operator=(char *&s) -> self_type & {
   this->super_type::operator=(s);
   return *this;
 }
 
-inline TextView &
-TextView::operator=(char const *&s) {
+inline auto
+TextView::operator=(char const *&s) -> self_type & {
   this->super_type::operator=(s);
   return *this;
 }
 
-inline TextView &
-TextView::operator=(const std::string &s) {
+inline auto
+TextView::operator=(const std::string &s) -> self_type & {
   this->super_type::operator=(s);
   return *this;
 }
 
-inline TextView &
-TextView::assign(char * & c_str) {
+inline auto
+TextView::assign(char * & c_str) -> self_type & {
   return this->assign(c_str, strlen(c_str));
 }
 
-inline TextView &
-TextView::assign(char const * & c_str) {
+inline auto
+TextView::assign(char const * & c_str) -> self_type & {
   return this->assign(c_str, strlen(c_str));
 }
 
-inline TextView &
-TextView::assign(const std::string &s) {
+inline auto
+TextView::assign(const std::string &s) -> self_type & {
   *this = super_type(s);
   return *this;
 }
@@ -1081,8 +1088,8 @@
   return *this = self_type{s, s[N - 1] ? N : N - 1};
 }
 
-inline constexpr TextView
-TextView::prefix(size_t n) const noexcept {
+inline constexpr auto
+TextView::prefix(size_t n) const noexcept -> self_type {
   return {this->data(), std::min(n, this->size())};
 }
 
@@ -1091,8 +1098,8 @@
   return {this->data(), std::min<size_t>(n, this->size())};
 }
 
-inline TextView
-TextView::prefix_at(char c) const {
+inline auto
+TextView::prefix_at(char c) const -> self_type {
   self_type zret; // default to empty return.
   if (auto n = this->find(c); n != npos) {
     zret.assign(this->data(), n);
@@ -1110,8 +1117,8 @@
 }
 
 template <typename F>
-TextView::self_type
-TextView::prefix_if(F const &pred) const {
+auto
+TextView::prefix_if(F const &pred) const -> self_type {
   self_type zret; // default to empty return.
   if (auto n = this->find_if(pred); n != npos) {
     zret.assign(this->data(), n);
@@ -1133,8 +1140,8 @@
   return *this;
 }
 
-inline TextView &
-TextView::remove_prefix_at(std::string_view const &delimiters) {
+inline auto
+TextView::remove_prefix_at(std::string_view const &delimiters) -> self_type & {
   if (auto n = this->find_first_of(delimiters); n != npos) {
     this->super_type::remove_prefix(n + 1);
   }
@@ -1142,8 +1149,8 @@
 }
 
 template <typename F>
-TextView::self_type &
-TextView::remove_prefix_if(F const &pred) {
+auto
+TextView::remove_prefix_if(F const &pred) -> self_type & {
   if (auto n = this->find_if(pred); n != npos) {
     this->super_type::remove_prefix(n + 1);
   }
@@ -1200,8 +1207,8 @@
 }
 
 template <typename F>
-TextView::self_type
-TextView::take_prefix_if(F const &pred) {
+auto
+TextView::take_prefix_if(F const &pred) -> self_type {
   return this->take_prefix(this->find_if(pred));
 }
 
@@ -1237,8 +1244,8 @@
 }
 
 template <typename F>
-TextView::self_type
-TextView::suffix_if(F const &pred) const {
+auto
+TextView::suffix_if(F const &pred) const -> self_type {
   self_type zret;
   if (auto n = this->rfind_if(pred); n != npos) {
     ++n;
@@ -1334,7 +1341,7 @@
 
 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));
 }
 
@@ -1585,6 +1592,7 @@
   this->remove_suffix(idx);
   return token;
 }
+/// @endcond TextView_INTERNAL
 
 // Provide an instantiation for @c std::ostream as it's likely this is the only one ever used.
 extern template std::ostream &TextView::stream_write(std::ostream &, const TextView &) const;
@@ -1753,7 +1761,7 @@
  * @tparam V The source type.
  *
  * 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.
+ * required in general but not in all cases.
  */
 template <typename V> class TransformView<void, V> {
   using self_type = TransformView; ///< Self reference type.
@@ -1939,6 +1947,11 @@
   using difference_type   = ssize_t;
   using iterator_category = forward_iterator_tag;
 };
+
+template <> struct hash<swoc::TextView> {
+  static constexpr hash<string_view> super_hash{};
+  size_t operator()(swoc::TextView const& s) const { return super_hash(s); }
+};
 /// @endcond
 
 } // namespace std
diff --git a/lib/swoc/include/swoc/Vectray.h b/lib/swoc/include/swoc/Vectray.h
index 42f7a37..75eb394 100644
--- a/lib/swoc/include/swoc/Vectray.h
+++ b/lib/swoc/include/swoc/Vectray.h
@@ -36,19 +36,19 @@
 template < typename T, size_t N, class A = std::allocator<T> >
 class Vectray {
   using self_type = Vectray; ///< Self reference type.
-  using vector_type = std::vector<T, A>;
+  using vector_type = std::vector<T, A>; ///< Internal dynamic storage type.
 
 public: // STL compliance types.
-  using value_type = T;
-  using reference = std::remove_reference<T>&;
-  using const_reference = std::remove_reference<T> const&;
-  using pointer = std::remove_reference<T>*;
-  using const_pointer = std::remove_reference<T> const*;
-  using allocator_type = A;
-  using size_type = typename vector_type::size_type;
-  using difference_type = typename vector_type::difference_type;
-  using iterator = typename swoc::MemSpan<T>::iterator;
-  using const_iterator = typename swoc::MemSpan<const T>::iterator;
+  using value_type = T; ///< Element type for container.
+  using reference = std::remove_reference<T>&; ///< Reference to element.
+  using const_reference = std::remove_reference<T> const&; ///< Reference to constant element.
+  using pointer = std::remove_reference<T>*; ///< Pointer to element.
+  using const_pointer = std::remove_reference<T> const*; ///< Pointer to constant element.
+  using allocator_type = A; ///< Dynamic storage allocator.
+  using size_type = typename vector_type::size_type; ///< Type for element count.
+  using difference_type = typename vector_type::difference_type; ///< Iterator difference.
+  using iterator = typename swoc::MemSpan<T>::iterator; ///< Element iteration.
+  using const_iterator = typename swoc::MemSpan<const T>::iterator; ///< Constant element iteration.
   // Need to add reverse iterators - @c reverse_iterator and @c const_reverse_iterator
 
   /// Internal (fixed) storage.
@@ -79,8 +79,9 @@
 
   using DynamicStore = vector_type; ///< Dynamic (heap) storage.
 
-  /// Generic form for referencing stored objects.
+  /// Element access.
   using span = swoc::MemSpan<T>;
+  /// Constant element access.
   using const_span = swoc::MemSpan<T const>;
 
 public:
@@ -100,6 +101,7 @@
    */
   explicit Vectray(size_type n, allocator_type const& alloc = allocator_type{});
 
+  /// Move constructor for difference static sized instance.
   template < size_t M > Vectray(Vectray<T, M, A> && that);
 
   /// Move constructor.
@@ -157,14 +159,14 @@
   }
   /** Append an element by copy.
    *
-   * @param src Element to add.
+   * @param t Element to add.
    * @return @a this.
    */
   self_type& push_back(T const& t);
 
   /** Append an element by move.
    *
-   * @param src Element to add.
+   * @param t Element to add.
    * @return @a this.
    */
   self_type& push_back(T && t);
@@ -175,7 +177,7 @@
    * @param args Constructor arguments.
    * @return @a this
    */
-  template < typename ... Args> self_type& emplace_back(Args && ... args);
+  template < typename ... Args> self_type & emplace_back(Args && ... args);
 
   /** Remove an element from the end of the current elements.
    *
@@ -261,7 +263,7 @@
 
 template<typename T, size_t N, class A>
 MemSpan<T> Vectray<T, N, A>::FixedStore::span() {
-  return MemSpan(_raw).template rebind<T>();
+  return MemSpan<std::byte>(_raw).template rebind<T>();
 }
 
 template<typename T, size_t N, class A>
@@ -322,7 +324,7 @@
 
 template<typename T, size_t N, class A>
 template<typename... Args>
-auto Vectray<T, N, A>::emplace_back(Args && ... args) -> self_type& {
+typename Vectray<T,N,A>::self_type & Vectray<T, N, A>::emplace_back(Args && ... args) {
   if (_store.index() == FIXED) {
     auto& fs{std::get<FIXED>(_store)};
     if (fs._count < N) {
@@ -355,7 +357,7 @@
 template<typename T, size_t N, typename A>
 bool Vectray<T,N,A>::empty() const {
   return std::visit(swoc::meta::vary{
-           [](FixedStore const& fs) { return fs._count > 0; }
+           [](FixedStore const& fs) { return fs._count == 0; }
          , [](DynamicStore const& ds) { return ds.empty(); }
          }, _store);
 }
diff --git a/lib/swoc/include/swoc/bwf_base.h b/lib/swoc/include/swoc/bwf_base.h
index a13e18f..5c122b4 100644
--- a/lib/swoc/include/swoc/bwf_base.h
+++ b/lib/swoc/include/swoc/bwf_base.h
@@ -118,7 +118,7 @@
     static constexpr uint8_t UPPER_TYPE_CHAR   = 0x20; ///< Upper case flag.
     static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output.
     static constexpr uint8_t SIGN_CHAR         = 0x80; ///< Is sign character.
-  } _prop;
+  } _prop; ///< Character property map.
 };
 
 /** Format string support.
@@ -172,8 +172,15 @@
   struct FormatExtractor {
     const std::vector<Spec> &_fmt; ///< Parsed format string.
     int _idx = 0;                  ///< Element index.
+    /// @return @c true if more format string, @c false if none.
     explicit operator bool() const;
 
+    /** Extract next formatting elements.
+     *
+     * @param literal_v [out] The next literal.
+     * @param spec [out] The next format specifier.
+     * @return @c true if @a spec was filled out, @c false if no specifier was found.
+     */
     bool operator()(std::string_view &literal_v, Spec &spec);
   };
 
@@ -278,8 +285,9 @@
   /// Copy @a name in to local storage and return a view of it.
   std::string_view localize(std::string_view const &name);
 
+  /// Name to name generator.
   using Map = std::unordered_map<std::string_view, Generator>;
-  Map _map;              ///< Mapping of name -> generator
+  Map _map;              ///< Defined generators.
   MemArena _arena{1024}; ///< Local name storage.
 };
 
@@ -341,6 +349,7 @@
 
   using super_type::super_type; // inherit @c super_type constructors.
 
+  /// Specialized binding for names in an instance of @c ContextNames
   class Binding : public NameBinding {
   public:
     /** Override of virtual method to provide an implementation.
@@ -358,12 +367,17 @@
     }
 
   protected:
+    /** Constructor.
+     *
+     * @param names Names to define for the binding.
+     * @param ctx Binding context.
+     */
     Binding(ContextNames const &names, context_type &ctx) : _ctx(ctx), _names(names) {}
 
     context_type &_ctx;         ///< Context for generators.
     ContextNames const &_names; ///< Base set of names.
 
-    friend ContextNames;
+    friend class ContextNames;
   };
 
   /** Assign the external generator @a bg to @a name.
@@ -900,34 +914,38 @@
 
 // ---- Formatting for specific types.
 
-// Must be first because it is used by other formatters, and is not inline.
+/** Output a @c string_view.
+ *
+ * @param w Output
+ * @param spec Format specifier
+ * @param sv View to format.
+ * @return @a w
+ *
+ * @internal Must be first because it is used by other formatters, and is not inline.
+ */
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv);
 
-// Pointers that are not specialized.
-inline BufferWriter &
-bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) {
-  using namespace swoc::literals;
-  bwf::Spec ptr_spec{spec};
-  ptr_spec._radix_lead_p = true;
+/** Format non-specialized pointers.
+ *
+ * @param w Output
+ * @param spec Format specifier.
+ * @param ptr Pointer to format.
+ * @return @a w
+ *
+ * Non-specialized pointers are formatted simply as pointers, rather than the pointed to data.
+ */
+BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr);
 
-  if (ptr == nullptr) {
-    if (spec._type == 's' || spec._type == 'S') {
-      ptr_spec._type = bwf::Spec::DEFAULT_TYPE;
-      ptr_spec._ext  = ""_sv; // clear any extension.
-      return bwformat(w, spec, spec._type == 's' ? "null"_sv : "NULL"_sv);
-    } else if (spec._type == bwf::Spec::DEFAULT_TYPE) {
-      return w; // print nothing if there is no format character override.
-    }
-  }
-
-  if (ptr_spec._type == bwf::Spec::DEFAULT_TYPE || ptr_spec._type == 'p') {
-    ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex.
-  } else if (ptr_spec._type == 'P') {
-    ptr_spec._type = 'X'; // P means upper hex, overriding other specializations.
-  }
-  return bwf::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false);
-}
-
+/** Format a generic (void) memory span.
+ *
+ * @param w Output
+ * @param spec Format specifier.
+ * @param span Span to format.
+ * @return @a w
+ *
+ * The format is by default "N:ptr" where N is the size and ptr is a hex formatter pointer. If the
+ * format is "x" or "X" the span content is dumped as contiguous hex.
+ */
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void> const &span);
 
 template <typename T>
@@ -1194,6 +1212,13 @@
 
 } // namespace bwf
 
+/** Format a hex dump.
+ *
+ * @param w The output.
+ * @param spec Format specifier.
+ * @param hex Hex dump wrapper.
+ * @return @a w
+ */
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex);
 
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/include/swoc/bwf_ex.h b/lib/swoc/include/swoc/bwf_ex.h
index 318f3ee..c232591 100644
--- a/lib/swoc/include/swoc/bwf_ex.h
+++ b/lib/swoc/include/swoc/bwf_ex.h
@@ -32,8 +32,9 @@
  * is type 'd' then just the numeric value is printed.
  */
 struct Errno {
-  int _e;
+  int _e; ///< Errno value.
 
+  /// Construct wrapper, default to current @c errno
   explicit Errno(int e = errno) : _e(e) {}
 };
 
@@ -42,12 +43,19 @@
  * provided a format like "2017 Jun 29 14:11:29" is used.
  */
 struct Date {
+  /// Default format
   static constexpr std::string_view DEFAULT_FORMAT{"%Y %b %d %H:%M:%S"_sv};
-  time_t _epoch;
-  std::string_view _fmt;
+  time_t _epoch; ///< The time.
+  std::string_view _fmt; ///< Data format.
 
+  /** Constructor.
+   *
+   * @param t The timestamp.
+   * @param fmt Timestamp format.
+   */
   Date(time_t t, std::string_view fmt = DEFAULT_FORMAT) : _epoch(t), _fmt(fmt) {}
 
+  /// Default construct using current time with optional format.
   Date(std::string_view fmt = DEFAULT_FORMAT);
 };
 
@@ -200,12 +208,51 @@
 }
 } // namespace bwf
 
+/** Repeatedly output a pattern.
+ *
+ * @param w Output.
+ * @param spec Format specifier.
+ * @param pattern Output patterning.
+ * @return @a w
+ *
+ * The @a pattern contains the count and text to output.
+ */
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern);
 
+/** Format an integer as an @c errno value.
+ *
+ * @param w Output.
+ * @param spec Format specifier.
+ * @param e Error code.
+ * @return @a w
+ */
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e);
 
+/** Format a timestamp wrapped in a @c Date.
+ *
+ * @param w Output.
+ * @param spec Format specifier.
+ * @param date Timestamp.
+ * @return @a w
+ */
 BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date);
 
+/** Output a nested formatted string.
+ *
+ * @tparam Args Argument pack for @a subtext.
+ * @param w Output
+ * @param subtext Format string and arguments.
+ * @return @a w
+ *
+ * This supports a nested format string and arguments inside another format string. This is most often useful
+ * if one of the formats is fixed or pre-compiled.
+ *
+ * @code
+ * bwformat(w, "Line {} offset {} with data {}.", line_no, line_off, bwf::SubText("alpha {} bravo {}", alpha, bravo"));
+ * @endcode
+ *
+ * @see bwf::Subtext
+ */
 template <typename... Args>
 BufferWriter &
 bwformat(BufferWriter &w, bwf::Spec const &, bwf::SubText<Args...> const &subtext) {
diff --git a/lib/swoc/include/swoc/bwf_fwd.h b/lib/swoc/include/swoc/bwf_fwd.h
index f7a9054..59fe601 100644
--- a/lib/swoc/include/swoc/bwf_fwd.h
+++ b/lib/swoc/include/swoc/bwf_fwd.h
@@ -9,7 +9,7 @@
 
 #include "swoc/swoc_version.h"
 
-namespace SWOC_NAMESPACE {
+namespace swoc { inline namespace SWOC_VERSION_NS {
 class BufferWriter;
 class FixedBufferWriter;
 template <size_t N> class LocalBufferWriter;
@@ -18,4 +18,4 @@
 struct Spec;
 class Format;
 } // namespace bwf
-} // namespace SWOC_NAMESPACE
+}} // namespace swoc
diff --git a/include/tscpp/util/string_view_util.h b/lib/swoc/include/swoc/string_view_util.h
similarity index 68%
rename from include/tscpp/util/string_view_util.h
rename to lib/swoc/include/swoc/string_view_util.h
index 5be1cd4..c47712b 100644
--- a/include/tscpp/util/string_view_util.h
+++ b/lib/swoc/include/swoc/string_view_util.h
@@ -1,27 +1,17 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Apache Software Foundation 2019
 /** @file
 
-   Utility overloads for @c std::string_view
-
-   @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.
- */
+    Additional handy utilities for @c string_view and hence also @c TextView.
+*/
 
 #pragma once
+#include <bitset>
+#include <iosfwd>
+#include <memory.h>
+#include <string>
 #include <string_view>
-#include <string.h>
-#include <strings.h>
+#include <limits>
 
 /** Compare views with ordering, ignoring case.
  *
@@ -70,8 +60,7 @@
  * @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);
 }
 
@@ -93,7 +82,6 @@
  *
  */
 inline void *
-memcpy(void *dst, const std::string_view &src)
-{
+memcpy(void *dst, const std::string_view &src) {
   return memcpy(dst, src.data(), src.size());
 }
diff --git a/lib/swoc/include/swoc/swoc_file.h b/lib/swoc/include/swoc/swoc_file.h
index 8e3c68e..33d13a9 100644
--- a/lib/swoc/include/swoc/swoc_file.h
+++ b/lib/swoc/include/swoc/swoc_file.h
@@ -22,9 +22,10 @@
 /** Utility class for file system paths.
  */
 class path {
-  using self_type = path;
+  using self_type = path; ///< Self reference type.
 
 public:
+  /// Default path separator.
   static constexpr char SEPARATOR = '/';
 
   /// Default construct empty path.
@@ -65,6 +66,14 @@
    */
   self_type &operator/=(const self_type &that);
 
+  /** Append or replace path with @a that.
+   *
+   * If @a that is absolute, it replaces @a this. Otherwise @a that is appended with exactly one
+   * separator.
+   *
+   * @param that Filesystem path.
+   * @return @a this
+   */
   self_type &operator/=(std::string_view that);
 
   /// Check if the path is empty.
@@ -76,6 +85,7 @@
   /// Check if the path is not absolute.
   bool is_relative() const;
 
+  /// Path of the parent.
   self_type parent_path() const;
 
   /// Access the path explicitly.
@@ -154,8 +164,11 @@
  */
 path absolute(path const &src, std::error_code &ec);
 
+/// @return The modified time for @a fs.
 std::chrono::system_clock::time_point modify_time(file_status const &fs);
+/// @return The access time for @a fs.
 std::chrono::system_clock::time_point access_time(file_status const &fs);
+/// @return The status change time for @a fs.
 std::chrono::system_clock::time_point status_time(file_status const &fs);
 
 /** Load the file at @a p into a @c std::string.
@@ -215,35 +228,55 @@
   return *this /= std::string_view(that._path);
 }
 
+/** Compare two paths.
+ *
+ * @return @c true if @a lhs is identical to @a rhs.
+ */
 inline bool
 operator==(path const &lhs, path const &rhs) {
   return lhs.view() == rhs.view();
 }
 
+/** Compare two paths.
+ *
+ * @return @c true if @a lhs is not identical to @a rhs.
+ */
 inline bool
 operator!=(path const &lhs, path const &rhs) {
   return lhs.view() != rhs.view();
 }
 
 /** Combine two strings as file paths.
-
-     @return A @c path with the combined path.
-*/
+ *
+ * @return A @c path with the combined path.
+ */
 inline path
 operator/(const path &lhs, const path &rhs) {
   return path(lhs) /= rhs;
 }
 
+/** Combine two strings as file paths.
+ *
+ * @return A @c path with the combined path.
+ */
 inline path
 operator/(path &&lhs, const path &rhs) {
   return path(std::move(lhs)) /= rhs;
 }
 
+/** Combine two strings as file paths.
+ *
+ * @return A @c path with the combined path.
+ */
 inline path
 operator/(const path &lhs, std::string_view rhs) {
   return path(lhs) /= rhs;
 }
 
+/** Combine two strings as file paths.
+ *
+ * @return A @c path with the combined path.
+ */
 inline path
 operator/(path &&lhs, std::string_view rhs) {
   return path(std::move(lhs)) /= rhs;
diff --git a/lib/swoc/include/swoc/swoc_ip.h b/lib/swoc/include/swoc/swoc_ip.h
index 2132c73..f9f0cfb 100644
--- a/lib/swoc/include/swoc/swoc_ip.h
+++ b/lib/swoc/include/swoc/swoc_ip.h
@@ -1,5 +1,4 @@
 // SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: Apache-2.0
 // Copyright Network Geographics 2014
 /** @file
    IP address and network related classes.
@@ -15,3361 +14,37 @@
 
 #include "swoc/swoc_version.h"
 #include "swoc/TextView.h"
-#include "swoc/DiscreteRange.h"
-#include "swoc/RBTree.h"
+
+#include "swoc/IPEndpoint.h"
+#include "swoc/IPAddr.h"
+#include "swoc/IPSrv.h"
+#include "swoc/IPRange.h"
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 
-class IP4Addr;
-
-class IP6Addr;
-
-class IPAddr;
-
-class IPMask;
-
-class IP4Range;
-
-class IP6Range;
-
-class IPRange;
-
-class IP4Net;
-
-class IP6Net;
-
-class IPNet;
-
-using ::std::string_view;
-
-namespace detail {
-  extern void *const pseudo_nullptr;
-}
-
-/** 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
-    instances. These are all duplicates of methods that operate on the internal @c sockaddr and
-    are provided primarily for backwards compatibility during the shift to using this class.
-
-    We use the term "endpoint" because these contain more than just the raw address, all of the data
-    for an IP endpoint is present.
- */
-union IPEndpoint {
-  using self_type = IPEndpoint; ///< Self reference type.
-
-  struct sockaddr sa;      ///< Generic address.
-  struct sockaddr_in sa4;  ///< IPv4
-  struct sockaddr_in6 sa6; ///< IPv6
-
-  /// Default construct invalid instance.
-  IPEndpoint();
-  IPEndpoint(self_type const &that); ///< Copy constructor.
-  ~IPEndpoint() = default;
-
-  /// Construct from the @a text representation of an address.
-  IPEndpoint(string_view const &text);
-
-  // Construct from @c IPAddr
-  explicit IPEndpoint(IPAddr const &addr);
-
-  // Construct from @c sockaddr
-  IPEndpoint(sockaddr const *sa);
-
-  /// Copy assignment.
-  self_type &operator=(self_type const &that);
-
-  /** Break a string in to IP address relevant tokens.
-   *
-   * @param src Source text. [in]
-   * @param host The host / address. [out]
-   * @param port The port. [out]
-   * @param rest Any text past the end of the IP address. [out]
-   * @return @c true if an IP address was found, @c false otherwise.
-   *
-   * Any of the out parameters can be @c nullptr in which case they are not updated.
-   * This parses and discards the IPv6 brackets.
-   *
-   * @note This is intended for internal use to do address parsing, but it can be useful in other contexts.
-   */
-  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.
-
-      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(string_view const &str);
-
-  /// Invalidate a @c sockaddr.
-  static void invalidate(sockaddr *addr);
-
-  /// Invalidate this endpoint.
-  self_type &invalidate();
-
-  /** Copy (assign) the contents of @a src to @a dst.
-   *
-   * The caller must ensure @a dst is large enough to hold the contents of @a src, the size of which
-   * can vary depending on the type of address in @a dst.
-   *
-   * @param dst Destination.
-   * @param src Source.
-   * @return @c true if @a dst is a valid IP address, @c false otherwise.
-   */
-  static bool assign(sockaddr *dst, sockaddr const *src);
-
-  /** Assign from a socket address.
-      The entire address (all parts) are copied if the @a ip is valid.
-  */
-  self_type &assign(sockaddr const *addr);
-
-  /// Assign from an @a addr and @a port.
-  self_type &assign(IPAddr const &addr, in_port_t port = 0);
-
-  /// Copy to @a sa.
-  const self_type &copy_to(sockaddr *addr) const;
-
-  /// Test for valid IP address.
-  bool is_valid() const;
-
-  /// Test for IPv4.
-  bool is_ip4() const;
-
-  /// Test for IPv6.
-  bool is_ip6() const;
-
-  /** Effectively size of the address.
-   *
-   * @return The size of the structure appropriate for the address family of the stored address.
-   */
-  socklen_t size() const;
-
-  /// @return The IP address family.
-  sa_family_t family() const;
-
-  /// Set to be the ANY address for family @a family.
-  /// @a family must be @c AF_INET or @c AF_INET6.
-  /// @return This object.
-  self_type &set_to_any(int family);
-
-  /// @return @c true if this is the ANY address, @c false if not.
-  bool is_any() const;
-
-  /// Set to be loopback address for family @a family.
-  /// @a family must be @c AF_INET or @c AF_INET6.
-  /// @return This object.
-  self_type &set_to_loopback(int family);
-
-  /// @return @c true if this is a loopback address, @c false if not.
-  bool is_loopback() const;
-
-  /// Port in network order.
-  in_port_t &network_order_port();
-
-  /// Port in network order.
-  in_port_t network_order_port() const;
-
-  /// Port in host horder.
-  in_port_t host_order_port() const;
-
-  /// Port in network order from @a sockaddr.
-  static in_port_t &port(sockaddr *sa);
-
-  /// Port in network order from @a sockaddr.
-  static in_port_t port(sockaddr const *sa);
-
-  /// Port in host order directly from a @c sockaddr
-  static in_port_t host_order_port(sockaddr const *sa);
-
-  /// Automatic conversion to @c sockaddr.
-  operator sockaddr *() { return &sa; }
-
-  /// Automatic conversion to @c sockaddr.
-  operator sockaddr const *() const { return &sa; }
-
-  /// The string name of the address family.
-  static string_view family_name(sa_family_t family);
-};
-
-/** Storage for an IPv4 address.
-    Stored in host order.
- */
-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 constexpr size_t WIDTH = std::numeric_limits<unsigned char>::digits * SIZE; ///< # of bits in an address.
-
-  static const self_type MIN;                                                        ///< Minimum value.
-  static const self_type MAX;                                                        ///< Maximum value.
-  static constexpr sa_family_t AF_value = AF_INET;                                   ///< Address family type.
-
-  constexpr IP4Addr() = default; ///< Default constructor - minimum address.
-
-  /// Construct using IPv4 @a addr (in host order).
-  /// @note Host order seems odd, but all of the standard network macro values such as @c INADDR_LOOPBACK
-  /// are in host order.
-  explicit constexpr IP4Addr(in_addr_t addr);
-
-  /// Construct from @c sockaddr_in.
-  explicit IP4Addr(sockaddr_in const *sa);
-
-  /// Construct from text representation.
-  /// If the @a text is invalid the result is an invalid instance.
-  IP4Addr(string_view const &text);
-
-  /// Construct from generic address @a addr.
-  explicit IP4Addr(IPAddr const &addr);
-
-  /// Assign from IPv4 raw address.
-  self_type &operator=(in_addr_t ip);
-
-  /// Set to the address in @a addr.
-  self_type &operator=(sockaddr_in const *sa);
-
-  /// Increment address.
-  self_type &operator++();
-
-  /// Decrement address.
-  self_type &operator--();
-
-  /** Byte access.
-   *
-   * @param idx Byte index.
-   * @return The byte at @a idx in the address.
-   */
-  uint8_t
-  operator[](unsigned idx) const {
-    return reinterpret_cast<bytes const &>(_addr)[idx];
-  }
-
-  /// Apply @a mask to address, leaving the network portion.
-  self_type &operator&=(IPMask const &mask);
-
-  /// Apply @a mask to address, creating the broadcast address.
-  self_type &operator|=(IPMask const &mask);
-
-  /// Write this adddress and @a port to the sockaddr @a sa.
-  sockaddr_in *copy_to(sockaddr_in *sa, in_port_t port = 0) const;
-
-  /// @return The address in network order.
-  in_addr_t network_order() const;
-
-  /// @return The address in host order.
-  in_addr_t host_order() const;
-
-  /** Parse @a text as IPv4 address.
-      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 load(string_view const &text);
-
-  /// Standard ternary compare.
-  int cmp(self_type const &that) const;
-
-  /// Get the IP address family.
-  /// @return @c AF_INET
-  /// @note Useful primarily for template classes.
-  constexpr sa_family_t family();
-
-  /// @return @c true if this is the "any" address, @c false if not.
-  bool is_any() const;
-
-  /// @return @c true if this is a multicast address, @c false if not.
-  bool is_multicast() const;
-
-  /// @return @c true if this is a loopback address, @c false if not.
-  bool is_loopback() const;
-
-  /** Left shift.
-   *
-   * @param n Number of bits to shift left.
-   * @return @a this.
-   */
-  self_type &operator<<=(unsigned n);
-
-  /** Right shift.
-   *
-   * @param n Number of bits to shift right.
-   * @return @a this.
-   */
-  self_type &operator>>=(unsigned n);
-
-  /** Bitwise AND.
-   *
-   * @param that Source address.
-   * @return @a this.
-   *
-   * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that.
-   */
-  self_type &operator&=(self_type const &that);
-
-  /** Bitwise OR.
-   *
-   * @param that Source address.
-   * @return @a this.
-   *
-   * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that.
-   */
-  self_type &operator|=(self_type const &that);
-
-  /** Convert between network and host order.
-   *
-   * @param src Input address.
-   * @return @a src with the byte reversed.
-   *
-   * This performs the same computation as @c ntohl and @c htonl but is @c constexpr to be usable
-   * in situations those two functions are not.
-   */
-  constexpr static in_addr_t reorder(in_addr_t src);
-
-protected:
-  /// Access by bytes.
-  using bytes = std::array<uint8_t, 4>;
-
-  friend bool operator==(self_type const &, self_type const &);
-
-  friend bool operator!=(self_type const &, self_type const &);
-
-  friend bool operator<(self_type const &, self_type const &);
-
-  friend bool operator<=(self_type const &, self_type const &);
-
-  in_addr_t _addr = INADDR_ANY; ///< Address in host order.
-};
-
-/** Storage for an IPv6 address.
-    Internal storage is not necessarily network ordered.
-    @see network_order
-    @see copy_to
- */
-class IP6Addr {
-  using self_type = IP6Addr; ///< Self reference type.
-
-  friend class IP6Range;
-  friend class IPMask;
-
-public:
-  static constexpr size_t WIDTH         = 128;                                          ///< Number of bits in the address.
-  static constexpr size_t SIZE          = WIDTH / std::numeric_limits<uint8_t>::digits; ///< Size of address in bytes.
-  static constexpr sa_family_t AF_value = AF_INET6;                                     ///< Address family type.
-
-  using quad_type                 = uint16_t;                 ///< Size of one segment of an IPv6 address.
-  static constexpr size_t N_QUADS = SIZE / sizeof(quad_type); ///< # of quads in an IPv6 address.
-  /// Number of bits per quad.
-  static constexpr size_t QUAD_WIDTH = std::numeric_limits<uint8_t>::digits * sizeof(quad_type);
-
-  /// Direct access type for the address.
-  /// Equivalent to the data type for data member @c s6_addr in @c in6_addr.
-  using raw_type = std::array<uint8_t, SIZE>;
-
-  /// Minimum value of an address.
-  static const self_type MIN;
-  /// Maximum value of an address.
-  static const self_type MAX;
-
-  IP6Addr()                      = default; ///< Default constructor - 0 address.
-  IP6Addr(self_type const &that) = default;
-
-  /// Construct using IPv6 @a addr.
-  explicit IP6Addr(in6_addr const &addr);
-
-  /// Construct from @c sockaddr_in.
-  explicit IP6Addr(sockaddr_in6 const *addr) { *this = addr; }
-
-  /// Construct from text representation.
-  /// If the @a text is invalid the result is an invalid instance.
-  IP6Addr(string_view const &text);
-
-  /// Construct from generic @a addr.
-  explicit IP6Addr(IPAddr const &addr);
-
-  /** Left shift.
-   *
-   * @param n Number of bits to shift left.
-   * @return @a this.
-   */
-  self_type &operator<<=(unsigned n);
-
-  /** Right shift.
-   *
-   * @param n Number of bits to shift right.
-   * @return @a this.
-   */
-  self_type &operator>>=(unsigned n);
-
-  /** Bitwise AND.
-   *
-   * @param that Source address.
-   * @return @a this.
-   *
-   * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that.
-   */
-  self_type &operator&=(self_type const &that);
-
-  /** Bitwise OR.
-   *
-   * @param that Source address.
-   * @return @a this.
-   *
-   * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that.
-   */
-  self_type &operator|=(self_type const &that);
-
-  /// Increment address.
-  self_type &operator++();
-
-  /// Decrement address.
-  self_type &operator--();
-
-  self_type & operator = (self_type const& that) = default;
-
-  /// Assign from IPv6 raw address.
-  self_type &operator=(in6_addr const &addr);
-
-  /// Set to the address in @a addr.
-  self_type &operator=(sockaddr_in6 const *addr);
-
-  /// 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;
-
-  /// Return the address in network order.
-  in6_addr network_order() const;
-
-  /** Parse a string for an IP address.
-
-      The address resuling 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 load(string_view const &str);
-
-  /// Generic three value compare.
-  int cmp(self_type const &that) const;
-
-  /// @return The address family.
-  constexpr sa_family_t family();
-
-  /// @return @c true if this is the "any" address, @c false if not.
-  bool is_any() const;
-
-  /// @return @c true if this is a loopback address, @c false if not.
-  bool is_loopback() const;
-
-  /// @return @c true if this is a multicast address, @c false if not.
-  bool is_multicast() const;
-
-  ///  @return @c true if this is an IPv4 addressed mapped to IPv6, @c false if not.
-  bool is_mapped_ipv4() const;
-
-  /** Reset to default constructed state.
-   *
-   * @return @a this
-   */
-  self_type & clear();
-
-  /** Bitwise AND.
-   *
-   * @param that Source address.
-   * @return @a this.
-   *
-   * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that.
-   */
-  self_type &operator&=(IPMask const &mask);
-
-  /** Bitwise OR.
-   *
-   * @param that Source address.
-   * @return @a this.
-   *
-   * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that.
-   */
-  self_type &operator|=(IPMask const &mask);
-
-  /** Convert between network and host ordering.
-   *
-   * @param dst Destination for re-ordered address.
-   * @param src Original address.
-   */
-  static void reorder(in6_addr &dst, raw_type const &src);
-
-  /** Convert between network and host ordering.
-   *
-   * @param dst Destination for re-ordered address.
-   * @param src Original address.
-   */
-  static void reorder(raw_type &dst, in6_addr const &src);
-
-  template < typename T > auto as_span() -> std::enable_if_t<swoc::meta::is_any_of_v<T, std::byte, uint8_t, uint16_t, uint32_t, uint64_t>, swoc::MemSpan<T>> {
-    return swoc::MemSpan(_addr._store).template rebind<T>();
-  }
-
-  template < typename T > auto as_span() const -> std::enable_if_t<swoc::meta::is_any_of_v<typename std::remove_const_t<T>, std::byte, uint8_t, uint16_t, uint32_t, uint64_t>, swoc::MemSpan<T const>> {
-    return swoc::MemSpan<uint64_t const>(_addr._store).template rebind<T const>();
-  }
-
-protected:
-  friend bool operator==(self_type const &, self_type const &);
-
-  friend bool operator!=(self_type const &, self_type const &);
-
-  friend bool operator<(self_type const &, self_type const &);
-
-  friend bool operator<=(self_type const &, self_type const &);
-
-  /// Direct access type for the address by quads (16 bits).
-  /// This corresponds to the elements of the text format of the address.
-  using quad_store_type = std::array<quad_type, N_QUADS>;
-
-  /// A bit mask of all 1 bits the size of a quad.
-  static constexpr quad_type QUAD_MASK = ~quad_type{0};
-
-  /// Type used as a "word", the natural working unit of the address.
-  using word_type = uint64_t;
-
-  static constexpr size_t WORD_SIZE = sizeof(word_type);
-
-  /// Number of bits per word.
-  static constexpr size_t WORD_WIDTH = std::numeric_limits<uint8_t>::digits * WORD_SIZE;
-
-  /// Number of words used for basic address storage.
-  static constexpr size_t N_STORE = SIZE / WORD_SIZE;
-
-  /// Type used to store the address.
-  using word_store_type = std::array<word_type, N_STORE>;
-
-  /// Type for digging around inside the address, with the various forms of access.
-  /// These are in sort of host order - @a _store elements are host order, but the
-  /// MSW and LSW are swapped (big-endian). This makes various bits of the implementation
-  /// easier. Conversion to and from network order is via the @c reorder method.
-  union {
-    word_store_type _store = {0}; ///< 0 is MSW, 1 is LSW.
-    quad_store_type _quad;        ///< By quad.
-    raw_type _raw;                ///< By byte.
-  } _addr;
-
-  static constexpr unsigned LSW = 1; ///< Least significant word index.
-  static constexpr unsigned MSW = 0; ///< Most significant word index.
-
-  /// Index of quads in @a _addr._quad.
-  /// This converts from the position in the text format to the quads in the binary format.
-  static constexpr std::array<unsigned, N_QUADS> QUAD_IDX = {3, 2, 1, 0, 7, 6, 5, 4};
-
-  /// Convert between network and host order.
-  /// The conversion is symmetric.
-  /// @param dst Output where reordered value is placed.
-  /// @param src Input value to resorder.
-  static void reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]);
-
-  /** Construct from two 64 bit values.
-   *
-   * @param msw The most significant 64 bits, host order.
-   * @param lsw The least significant 64 bits, host order.
-   */
-  IP6Addr(word_store_type::value_type msw, word_store_type::value_type lsw) : _addr{{msw, lsw}} {}
-
-  friend IP6Addr operator&(IP6Addr const &addr, IPMask const &mask);
-
-  friend IP6Addr operator|(IP6Addr const &addr, IPMask const &mask);
-};
-
-/** An IPv4 or IPv6 address.
- *
- * The family type is stored. For comparisons, invalid < IPv4 < IPv6. All invalid instances are equal.
- */
-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.
-  self_type & operator = (self_type const& that) = default; ///< Copy assignment.
-
-  /// Construct using IPv4 @a addr.
-  explicit IPAddr(in_addr_t addr);
-
-  /// Construct using an IPv4 @a addr
-  IPAddr(IP4Addr const &addr) : _addr{addr}, _family(IP4Addr::AF_value) {}
-
-  /// Construct using IPv6 @a addr.
-  explicit IPAddr(in6_addr const &addr);
-
-  /// construct using an IPv6 @a addr
-  IPAddr(IP6Addr const &addr) : _addr{addr}, _family(IP6Addr::AF_value) {}
-
-  /// 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(string_view const &text);
-
-  /// Set to the address in @a addr.
-  self_type &assign(sockaddr const *addr);
-
-  /// Set to the address in @a addr.
-  self_type &assign(sockaddr_in const *addr);
-
-  /// Set to the address in @a addr.
-  self_type &assign(sockaddr_in6 const *addr);
-
-  /// Set to IPv4 @a addr.
-  self_type &assign(in_addr_t addr);
-
-  /// Set to IPv6 @a addr
-  self_type &assign(in6_addr const &addr);
-
-  /// Assign from end point.
-  self_type &operator=(IPEndpoint const &ip);
-
-  /// Assign from IPv4 raw address.
-  self_type &operator=(in_addr_t ip);
-
-  /// Assign from IPv6 raw address.
-  self_type &operator=(in6_addr const &addr);
-
-  bool operator==(self_type const &that) const;
-
-  bool operator!=(self_type const &that) const;
-
-  bool operator<(self_type const &that) const;
-
-  bool operator>(self_type const &that) const;
-
-  bool operator<=(self_type const &that) const;
-
-  bool operator>=(self_type const &that) const;
-
-  /// Assign from @c sockaddr
-  self_type &operator=(sockaddr const *addr);
-
-  self_type &operator&=(IPMask const &mask);
-
-  self_type &operator|=(IPMask const &mask);
-
-  /** 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;
-
-  /// Test for same address family.
-  /// @c return @c true if @a that is the same address family as @a this.
-  bool isCompatibleWith(self_type const &that);
-
-  /// Get the address family.
-  /// @return The address family.
-  sa_family_t family() const;
-
-  /// Test for IPv4.
-  bool is_ip4() const;
-
-  /// Test for IPv6.
-  bool is_ip6() const;
-
-  IP4Addr const &ip4() const;
-
-  IP6Addr const &ip6() const;
-
-  explicit operator IP4Addr const &() const { return _addr._ip4; }
-
-  explicit
-  operator IP4Addr &() {
-    return _addr._ip4;
-  }
-
-  explicit operator IP6Addr const &() const { return _addr._ip6; }
-
-  explicit
-  operator IP6Addr &() {
-    return _addr._ip6;
-  }
-
-  /// Test for validity.
-  bool is_valid() const;
-
-  /// Make invalid.
-  self_type &invalidate();
-
-  /// Test for multicast
-  bool is_multicast() const;
-
-  /// Test for loopback
-  bool is_loopback() const;
-
-  ///< Pre-constructed invalid instance.
-  static self_type const INVALID;
-
-protected:
-  friend IP4Addr;
-  friend IP6Addr;
-
-  /// Address data.
-  union raw_addr_type {
-    IP4Addr _ip4;                                    ///< IPv4 address (host)
-    IP6Addr _ip6;                                    ///< IPv6 address (host)
-
-    constexpr raw_addr_type();
-
-    raw_addr_type(in_addr_t addr) : _ip4(addr) {}
-
-    raw_addr_type(in6_addr const &addr) : _ip6(addr) {}
-
-    raw_addr_type(IP4Addr const &addr) : _ip4(addr) {}
-
-    raw_addr_type(IP6Addr const &addr) : _ip6(addr) {}
-  } _addr;
-
-  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.
-  friend class IP4Addr;
-
-  friend class IP6Addr;
-
-public:
-  using raw_type = uint8_t; ///< Storage for mask width.
-
-  IPMask() = default; ///< Default construct to invalid mask.
-
-  /** Construct a mask of @a width.
-   *
-   * @param width Number of bits in the mask.
-   *
-   * @note Because this is a network mask, it is always left justified.
-   */
-  explicit IPMask(raw_type width);
-
-  /// @return @c true if the mask is valid, @c false if not.
-  bool is_valid() const;
-
-  /** Parse mask from @a text.
-   *
-   * @param text A number in string format.
-   * @return @a true if a valid CIDR value, @c false if not.
-   */
-  bool load(string_view const &text);
-
-  /** Copmute a mask for the network at @a addr.
-   * @param addr Lower bound of network.
-   * @return A mask with the width of the largest network starting at @a addr.
-   */
-  static self_type mask_for(IPAddr const &addr);
-
-  /** Copmute a mask for the network at @a addr.
-   * @param addr Lower bound of network.
-   * @return A mask with the width of the largest network starting at @a addr.
-   */
-  static self_type mask_for(IP4Addr const &addr);
-
-  /** Copmute a mask for the network at @a addr.
-   * @param addr Lower bound of network.
-   * @return A mask with the width of the largest network starting at @a addr.
-   */
-  static self_type mask_for(IP6Addr const &addr);
-
-  /// Change to default constructed state (invalid).
-  self_type & clear();
-
-  /// The width of the mask.
-  raw_type width() const;
-
-  /** Extend the mask (cover more addresses).
-   *
-   * @param n Number of bits to extend.
-   * @return @a this
-   *
-   * Effectively shifts the mask left, bringing in 0 bits on the right.
-   */
-  self_type & operator<<=(raw_type n);
-
-  /** Narrow the mask (cover fewer addresses).
-   *
-   * @param n Number of bits to narrow.
-   * @return @a this
-   *
-   * Effectively shift the mask right, bringing in 1 bits on the left.
-   */
-  self_type & operator>>=(raw_type n);
-
-  /** The mask as an IPv4 address.
-   *
-   * @return An IPv4 address that is the mask.
-   *
-   * If the mask is wider than an IPv4 address, the maximum mask is returned.
-   */
-  IP4Addr as_ip4() const;
-
-  /** The mask as an IPv6 address.
-   *
-   * @return An IPv6 address that is the mask.
-   *
-   * If the mask is wider than an IPv6 address, the maximum mask is returned.
-   */
-  IP6Addr as_ip6() const;
-
-protected:
-  /// Marker value for an invalid mask.
-  static constexpr auto INVALID = std::numeric_limits<raw_type>::max();
-
-  raw_type _cidr = INVALID; ///< Mask width in bits.
-
-  /// Compute a partial IPv6 mask, sized for the basic storage type.
-  static raw_type mask_for_quad(IP6Addr::quad_type q);
-};
-
-/** 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:
-  /// Default constructor, invalid range.
-  IP4Range() = default;
-
-  /// Construct from an network expressed as @a addr and @a mask.
-  IP4Range(IP4Addr const &addr, IPMask const &mask);
-
-  /// Construct from super type.
-  /// @internal Why do I have to do this, even though the super type constructors are inherited?
-  IP4Range(super_type const &r) : super_type(r) {}
-
-  /** Construct range from @a text.
-   *
-   * @param text Range text.
-   * @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 const &text);
-
-  using super_type::super_type; ///< Import super class constructors.
-
-  /** Set @a this range.
-   *
-   * @param addr Minimum address.
-   * @param mask CIDR mask to compute maximum adddress from @a addr.
-   * @return @a this
-   */
-  self_type &assign(IP4Addr const &addr, IPMask const &mask);
-
-  using super_type::assign; ///< Import assign methods.
-
-  /** Assign to this range from text.
-   *
-   * @param text Range text.
-   *
-   * The text must be in one of three formats.
-   * - A dashed range, "addr1-addr2"
-   * - A singleton, "addr". This is treated as if it were "addr-addr", a range of size 1.
-   * - CIDR notation, "addr/cidr" where "cidr" is a number from 0 to the number of bits in the address.
-   */
-  bool load(string_view text);
-
-  /** Compute the mask for @a this as a network.
-   *
-   * @return If @a this is a network, the mask for that network. Otherwise an invalid mask.
-   *
-   * @see IPMask::is_valid
-   */
-  IPMask network_mask() const;
-
-  class NetSource;
-
-  /** Generate a list of networks covering @a this range.
-   *
-   * @return A network generator.
-   *
-   * The returned object can be used as an iterator, or as a container to iterating over
-   * the unique minimal set of networks that cover @a this range.
-   *
-   * @code
-   * void (IP4Range const& range) {
-   *   for ( auto const& net : range ) {
-   *     net.addr(); // network address.
-   *     net.mask(); // network mask;
-   *   }
-   * }
-   * @endcode
-   */
-  NetSource networks() const;
-};
-
-/** Network generator class.
- * This generates networks from a range and acts as both a forward iterator and a container.
- */
-class IP4Range::NetSource {
-  using self_type = NetSource; ///< Self reference type.
-public:
-  using range_type = IP4Range; ///< Import base range type.
-
-  /// Construct from @a range.
-  explicit NetSource(range_type const &range);
-
-  /// Copy constructor.
-  NetSource(self_type const &that) = default;
-
-  /// This class acts as a container and an iterator.
-  using iterator = self_type;
-  /// All iteration is constant so no distinction between iterators.
-  using const_iterator = iterator;
-
-  iterator begin() const; ///< First network.
-  static iterator end() ;   ///< Past last network.
-
-  /// Return @c true if there are valid networks, @c false if not.
-  bool empty() const;
-
-  /// @return The current network.
-  IP4Net operator*() const;
-
-  /// Access @a this as if it were an @c IP4Net.
-  self_type *operator->();
-
-  /// Iterator support.
-  /// @return The current network address.
-  IP4Addr const &addr() const;
-
-  /// Iterator support.
-  /// @return The current network mask.
-  IPMask mask() const;
-
-  /// Move to next network.
-  self_type &operator++();
-
-  /// Move to next network.
-  self_type operator++(int);
-
-  /// Equality.
-  bool operator==(self_type const &that) const;
-
-  /// Inequality.
-  bool operator!=(self_type const &that) const;
-
-protected:
-  IP4Range _range; ///< Remaining range.
-  /// Mask for current network.
-  IP4Addr _mask{~static_cast<in_addr_t>(0)};
-  IPMask::raw_type _cidr = IP4Addr::WIDTH; ///< Current CIDR value.
-
-  void search_wider();
-
-  void search_narrower();
-
-  bool is_valid(IP4Addr mask) const;
-};
-
-class IP6Range : public DiscreteRange<IP6Addr> {
-  using self_type  = IP6Range;
-  using super_type = DiscreteRange<IP6Addr>;
-
-public:
-  /// Construct from super type.
-  /// @internal Why do I have to do this, even though the super type constructors are inherited?
-  IP6Range(super_type const &r) : super_type(r) {}
-
-  /** 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.
-   */
-  IP6Range(string_view const &text);
-
-  using super_type::super_type; ///< Import super class constructors.
-
-  /** Set @a this range.
-   *
-   * @param addr Minimum address.
-   * @param mask CIDR mask to compute maximum adddress from @a addr.
-   * @return @a this
-   */
-  self_type &assign(IP6Addr const &addr, IPMask const &mask);
-
-  using super_type::assign; ///< Import assign methods.
-
-  /** Assign to this range from text.
-   *
-   * @param text Range text.
-   *
-   * The text must be in one of three formats.
-   * - A dashed range, "addr1-addr2"
-   * - A singleton, "addr". This is treated as if it were "addr-addr", a range of size 1.
-   * - CIDR notation, "addr/cidr" where "cidr" is a number from 0 to the number of bits in the address.
-   */
-  bool load(string_view text);
-
-  /** Compute the mask for @a this as a network.
-   *
-   * @return If @a this is a network, the mask for that network. Otherwise an invalid mask.
-   *
-   * @see IPMask::is_valid
-   */
-  IPMask network_mask() const;
-
-  class NetSource;
-
-  /** Generate a list of networks covering @a this range.
-   *
-   * @return A network generator.
-   *
-   * The returned object can be used as an iterator, or as a container to iterating over
-   * the unique minimal set of networks that cover @a this range.
-   *
-   * @code
-   * void (IP6Range const& range) {
-   *   for ( auto const& net : range ) {
-   *     net.addr(); // network address.
-   *     net.mask(); // network mask;
-   *   }
-   * }
-   * @endcode
-   */
-  NetSource networks() const;
-};
-
-/** Network generator class.
- * This generates networks from a range and acts as both a forward iterator and a container.
- */
-class IP6Range::NetSource {
-  using self_type = NetSource; ///< Self reference type.
-public:
-  using range_type = IP6Range; ///< Import base range type.
-
-  /// Construct from @a range.
-  explicit NetSource(range_type const &range);
-
-  /// Copy constructor.
-  NetSource(self_type const &that) = default;
-
-  /// This class acts as a container and an iterator.
-  using iterator = self_type;
-  /// All iteration is constant so no distinction between iterators.
-  using const_iterator = iterator;
-
-  iterator begin() const; ///< First network.
-  iterator end() const;   ///< Past last network.
-
-  /// @return @c true if there are valid networks, @c false if not.
-  bool empty() const;
-
-  /// @return The current network.
-  IP6Net operator*() const;
-
-  /// Access @a this as if it were an @c IP6Net.
-  self_type *operator->();
-
-  /// @return The current network address.
-  IP6Addr const & addr() const;
-
-  /// Iterator support.
-  /// @return The current network mask.
-  IPMask mask() const;
-
-  /// Move to next network.
-  self_type &operator++();
-
-  /// Move to next network.
-  self_type operator++(int);
-
-  /// Equality.
-  bool operator==(self_type const &that) const;
-
-  /// Inequality.
-  bool operator!=(self_type const &that) const;
-
-protected:
-  IP6Range _range;              ///< Remaining range.
-  IPMask _mask{IP6Addr::WIDTH}; ///< Current CIDR value.
-
-  void search_wider();
-
-  void search_narrower();
-
-  bool is_valid(IPMask const &mask);
-};
-
-class IPRange {
-  using self_type = IPRange;
-
-public:
-  /// Default constructor - construct invalid range.
-  IPRange() = default;
-
-  IPRange(IPAddr const &min, IPAddr const &max);
-
-  /// Construct from an IPv4 @a range.
-  IPRange(IP4Range const &range);
-
-  /// Construct from an IPv6 @a range.
-  IPRange(IP6Range const &range);
-
-  /** Construct from a string format.
-   *
-   * @param text Text form of range.
-   *
-   * The string can be a single address, two addresses separated by a dash '-' or a CIDR network.
-   */
-  IPRange(string_view const &text);
-
-  /// Equality
-  bool
-  operator==(self_type const &that) const;
-
-  /// @return @c true if this is an IPv4 range, @c false if not.
-  bool
-  is_ip4() const {
-    return AF_INET == _family;
-  }
-
-  /// @return @c true if this is an IPv6 range, @c false if not.
-  bool
-  is_ip6() const {
-    return AF_INET6 == _family;
-  }
-
-  /** Check if @a this range is the IP address @a family.
-   *
-   * @param family IP address family.
-   * @return @c true if this is @a family, @c false if not.
-   */
-  bool is(sa_family_t family) const;
-
-  /** Load the range from @a text.
-   *
-   * @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);
-
-  /// @return The minimum address in the range.
-  IPAddr min() const;
-
-  /// @return The maximum address in the range.
-  IPAddr max() const;
-
-  bool empty() const;
-
-  IP4Range const &
-  ip4() const {
-    return _range._ip4;
-  }
-
-  IP6Range const &
-  ip6() const {
-    return _range._ip6;
-  }
-
-  /** Compute the mask for @a this as a network.
-   *
-   * @return If @a this is a network, the mask for that network. Otherwise an invalid mask.
-   *
-   * @see IPMask::is_valid
-   */
-  IPMask network_mask() const;
-
-  class NetSource;
-
-  /** Generate a list of networks covering @a this range.
-   *
-   * @return A network generator.
-   *
-   * The returned object can be used as an iterator, or as a container to iterating over
-   * the unique minimal set of networks that cover @a this range.
-   *
-   * @code
-   * void (IPRange const& range) {
-   *   for ( auto const& net : range ) {
-   *     net.addr(); // network address.
-   *     net.mask(); // network mask;
-   *   }
-   * }
-   * @endcode
-   */
-  NetSource networks() const;
-
-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};
-};
-
-/** Network generator class.
- * This generates networks from a range and acts as both a forward iterator and a container.
- */
-class IPRange::NetSource {
-  using self_type = NetSource; ///< Self reference type.
-public:
-  using range_type = IPRange; ///< Import base range type.
-
-  /// Construct from @a range.
-  explicit NetSource(range_type const &range);
-
-  /// Copy constructor.
-  NetSource(self_type const &that) = default;
-
-  /// This class acts as a container and an iterator.
-  using iterator = self_type;
-  /// All iteration is constant so no distinction between iterators.
-  using const_iterator = iterator;
-
-  iterator begin() const; ///< First network.
-  iterator end() const;   ///< Past last network.
-
-  /// @return The current network.
-  IPNet operator*() const;
-
-  /// Access @a this as if it were an @c IP6Net.
-  self_type *operator->();
-
-  /// Iterator support.
-  /// @return The current network address.
-  IPAddr addr() const;
-
-  /// Iterator support.
-  /// @return The current network mask.
-  IPMask mask() const;
-
-  /// Move to next network.
-  self_type &operator++();
-
-  /// Move to next network.
-  self_type operator++(int);
-
-  /// Equality.
-  bool operator==(self_type const &that) const;
-
-  /// Inequality.
-  bool operator!=(self_type const &that) const;
-
-protected:
-  union {
-    std::monostate _nil;
-    IP4Range::NetSource _ip4;
-    IP6Range::NetSource _ip6;
-  };
-  sa_family_t _family = AF_UNSPEC;
-};
-
-/// An IPv4 network.
-class IP4Net {
-  using self_type = IP4Net; ///< Self reference type.
-public:
-  IP4Net()                      = default; ///< Construct invalid network.
-  IP4Net(self_type const &that) = default; ///< Copy constructor.
-
-  /** Construct from @a addr and @a mask.
-   *
-   * @param addr An address in the network.
-   * @param mask The mask for the network.
-   *
-   * The network is based on the mask, and the resulting network address is chosen such that the
-   * network will contain @a addr. For a given @a addr and @a mask there is only one network
-   * that satisifies these criteria.
-   */
-  IP4Net(IP4Addr addr, IPMask mask);
-
-  IP4Net(swoc::TextView text) { this->load(text); }
-
-  /** Parse network as @a text.
-   *
-   * @param text String describing the network in CIDR format.
-   * @return @c true if a valid string, @c false if not.
-   */
-  bool load(swoc::TextView text);
-
-  /// @return @c true if the network is valid, @c false if not.
-  bool is_valid() const;
-
-  /// @return THh smallest address in the network.
-  IP4Addr lower_bound() const;
-
-  /// @return The largest address in the network.
-  IP4Addr upper_bound() const;
-
-  /// @return The mask for the network.
-  IPMask const &mask() const;
-
-  /// @return A range that exactly covers the network.
-  IP4Range as_range() const;
-
-  /** Assign an @a addr and @a mask to @a this.
-   *
-   * @param addr Network addres.
-   * @param mask Network mask.
-   * @return @a this.
-   */
-  self_type &assign(IP4Addr const &addr, IPMask const &mask);
-
-  /// Reset network to invalid state.
-  self_type &
-  clear() {
-    _mask.clear();
-    return *this;
-  }
-
-  /// Equality.
-  bool operator==(self_type const &that) const;
-
-  /// Inequality
-  bool operator!=(self_type const &that) const;
-
-protected:
-  IP4Addr _addr; ///< Network address (also lower_bound).
-  IPMask _mask;  ///< Network mask.
-};
-
-class IP6Net {
-  using self_type = IP6Net; ///< Self reference type.
-public:
-  IP6Net()                      = default; ///< Construct invalid network.
-  IP6Net(self_type const &that) = default; ///< Copy constructor.
-
-  /** Construct from @a addr and @a mask.
-   *
-   * @param addr An address in the network.
-   * @param mask The mask for the network.
-   *
-   * The network is based on the mask, and the resulting network address is chosen such that the
-   * network will contain @a addr. For a given @a addr and @a mask there is only one network
-   * that satisifies these criteria.
-   */
-  IP6Net(IP6Addr addr, IPMask mask);
-
-  /** Parse network as @a text.
-   *
-   * @param text String describing the network in CIDR format.
-   * @return @c true if a valid string, @c false if not.
-   */
-  bool load(swoc::TextView text);
-
-  /// @return @c true if the network is valid, @c false if not.
-  bool is_valid() const;
-
-  /// @return THh smallest address in the network.
-  IP6Addr lower_bound() const;
-
-  /// @return The largest address in the network.
-  IP6Addr upper_bound() const;
-
-  /// @return The mask for the network.
-  IPMask const &mask() const;
-
-  /// @return A range that exactly covers the network.
-  IP6Range as_range() const;
-
-  /** Assign an @a addr and @a mask to @a this.
-   *
-   * @param addr Network addres.
-   * @param mask Network mask.
-   * @return @a this.
-   */
-  self_type &assign(IP6Addr const &addr, IPMask const &mask);
-
-  /// Reset network to invalid state.
-  self_type &
-  clear() {
-    _mask.clear();
-    return *this;
-  }
-
-  /// Equality.
-  bool operator==(self_type const &that) const;
-
-  /// Inequality
-  bool operator!=(self_type const &that) const;
-
-protected:
-  IP6Addr _addr; ///< Network address (also lower_bound).
-  IPMask _mask;  ///< Network mask.
-};
-
-/** Representation of an IP address network.
- *
- */
-class IPNet {
-  using self_type = IPNet; ///< Self reference type.
-public:
-  IPNet()                      = default; ///< Construct invalid network.
-  IPNet(self_type const &that) = default; ///< Copy constructor.
-
-  /** Construct from @a addr and @a mask.
-   *
-   * @param addr An address in the network.
-   * @param mask The mask for the network.
-   *
-   * The network is based on the mask, and the resulting network address is chosen such that the
-   * network will contain @a addr. For a given @a addr and @a mask there is only one network
-   * that satisifies these criteria.
-   */
-  IPNet(IPAddr const &addr, IPMask const &mask);
-
-  IPNet(TextView text);
-
-  /** Parse network as @a text.
-   *
-   * @param text String describing the network in CIDR format.
-   * @return @c true if a valid string, @c false if not.
-   */
-  bool load(swoc::TextView text);
-
-  /// @return @c true if the network is valid, @c false if not.
-  bool is_valid() const;
-
-  /// @return THh smallest address in the network.
-  IPAddr lower_bound() const;
-
-  /// @return The largest address in the network.
-  IPAddr upper_bound() const;
-
-  IPMask::raw_type width() const;
-
-  /// @return The mask for the network.
-  IPMask const &mask() const;
-
-  /// @return A range that exactly covers the network.
-  IPRange as_range() const;
-
-  bool
-  is_ip4() const {
-    return _addr.is_ip4();
-  }
-
-  bool
-  is_ip6() const {
-    return _addr.is_ip6();
-  }
-
-  sa_family_t
-  family() const {
-    return _addr.family();
-  }
-
-  IP4Net
-  ip4() const {
-    return IP4Net{_addr.ip4(), _mask};
-  }
-
-  IP6Net
-  ip6() const {
-    return IP6Net{_addr.ip6(), _mask};
-  }
-
-  /** Assign an @a addr and @a mask to @a this.
-   *
-   * @param addr Network addres.
-   * @param mask Network mask.
-   * @return @a this.
-   */
-  self_type &assign(IPAddr const &addr, IPMask const &mask);
-
-  /// Reset network to invalid state.
-  self_type &
-  clear() {
-    _mask.clear();
-    return *this;
-  }
-
-  /// Equality.
-  bool operator==(self_type const &that) const;
-
-  /// Inequality
-  bool operator!=(self_type const &that) const;
-
-protected:
-  IPAddr _addr; ///< Address and family.
-  IPMask _mask; ///< Network 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.
-  using value_type = std::tuple<IPRange const, PAYLOAD &>;
-
-  /// Construct an empty space.
-  IPSpace() = default;
-
-  /** Mark the range @a r with @a payload.
-   *
-   * @param range 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(IPRange const &range, 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);
-
-  /** Erase addresses in @a range.
-   *
-   * @param range Address range.
-   * @return @a this
-   */
-  self_type &erase(IPRange const &range);
-
-  /** Blend @a color in to the @a range.
-   *
-   * @tparam F Blending functor type (deduced).
-   * @tparam U Data to blend in to payloads.
-   * @param range Target 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 , 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 color,
-   * it is assigned the default constructed @c PAYLOAD blended with @a color. If the address has a
-   * @c PAYLOAD @a p, @a p is updated by invoking <tt>blender(p, color)</tt>, with the expectation
-   * that @a p will be updated in place.
-   */
-  template <typename F, typename U = PAYLOAD> self_type &blend(IPRange const &range, U const &color, F &&blender);
-
-  template <typename F, typename U = PAYLOAD>
-  self_type & blend(IP4Range const &range, U const &color, F &&blender);
-
-  template <typename F, typename U = PAYLOAD>
-  self_type &
-  blend(IP6Range const &range, U const &color, F &&blender);
-
-  /// @return The number of distinct ranges.
-  size_t
-  count() const {
-    return _ip4.count() + _ip6.count();
-  }
-
-  size_t
-  count_ip4() const {
-    return _ip4.count();
-  }
-  size_t
-  count_ip6() const {
-    return _ip6.count();
-  }
-
-  size_t count(sa_family_t f) const;
-
-  /// Remove all ranges.
-  void clear();
-
-  /** Constant iterator.
-   * THe value type is a tuple of the IP address range and the @a PAYLOAD. Both are constant.
-   *
-   * @internal The non-const iterator is a subclass of this, in order to share implementation. This
-   * also makes it easy to convert from iterator to const iterator, which is desirable.
-   *
-   * @internal The return type is quite tricky because the value type of the nested containers is
-   * not the same as the value type for this container. It's not even a composite - @c IPRange is
-   * not an alias for either of the family specific range types. Therefore the iterator itself must
-   * contain a synthesized instance of the value type, which creates scoping and update problems.
-   * The approach here is to update the synthetic value when the iterator is modified and returning
-   * it by value for the dereference operator because a return by reference means  code like
-   * @code
-   *   auto && [ r , p ] = *(space.find(addr));
-   * @endcode
-   * can fail due to the iterator going out of scope after the statement is finished making @a r
-   * and @a p dangling references. If the return is by value the compiler takes care of it.
-   */
-  class const_iterator {
-    using self_type = const_iterator; ///< Self reference type.
-    friend class IPSpace;
-
-  public:
-    using value_type = std::tuple<IPRange const, PAYLOAD const &>; /// Import for API compliance.
-    // STL algorithm compliance.
-    using iterator_category = std::bidirectional_iterator_tag;
-    using pointer           = value_type *;
-    using reference         = value_type &;
-    using difference_type   = int;
-
-    /// Default constructor.
-    const_iterator() = default;
-
-    /// Copy constructor.
-    const_iterator(self_type const &that);
-
-    /// Assignment.
-    self_type &operator=(self_type const &that);
-
-    /// Pre-increment.
-    /// Move to the next element in the list.
-    /// @return The iterator.
-    self_type &operator++();
-
-    /// Pre-decrement.
-    /// Move to the previous element in the list.
-    /// @return The iterator.
-    self_type &operator--();
-
-    /// Post-increment.
-    /// Move to the next element in the list.
-    /// @return The iterator value before the increment.
-    self_type operator++(int);
-
-    /// Post-decrement.
-    /// Move to the previous element in the list.
-    /// @return The iterator value before the decrement.
-    self_type operator--(int);
-
-    /// Dereference.
-    /// @return A reference to the referent.
-    value_type operator*() const;
-
-    /// Dereference.
-    /// @return A pointer to the referent.
-    value_type const *operator->() const;
-
-    /// Equality
-    bool operator==(self_type const &that) const;
-
-    /// Inequality
-    bool operator!=(self_type const &that) const;
-
-  protected:
-    // These are stored non-const to make implementing @c iterator easier. The containing class provides the
-    // required @c const protection. This is basic a tuple of iterators - for forward iteration if
-    // the primary (ipv4) iterator is at the end, then use the secondary (ipv6) iterator. The reverse
-    // is done for reverse iteration. This depends on the extra support @c IntrusiveDList iterators
-    // provide.
-    typename IP4Space::iterator _iter_4; ///< IPv4 sub-space iterator.
-    typename IP6Space::iterator _iter_6; ///< IPv6 sub-space iterator.
-    /// Current value.
-    value_type _value{IPRange{}, *static_cast<PAYLOAD *>(detail::pseudo_nullptr)};
-
-    /** Internal constructor.
-     *
-     * @param iter4 Starting place for IPv4 subspace.
-     * @param iter6 Starting place for IPv6 subspace.
-     *
-     * In practice, both iterators should be either the beginning or ending iterator for the subspace.
-     */
-    const_iterator(typename IP4Space::iterator const &iter4, typename IP6Space::iterator const &iter6);
-  };
-
-  /** Iterator.
-   * The value type is a tuple of the IP address range and the @a PAYLOAD. The range is constant
-   * and the @a PAYLOAD is a reference. This can be used to update the @a PAYLOAD for this range.
-   *
-   * @note Range merges are not trigged by modifications of the @a PAYLOAD via an iterator.
-   */
-  class iterator : public const_iterator {
-    using self_type  = iterator;
-    using super_type = const_iterator;
-
-    friend class IPSpace;
-
-  public:
-  public:
-    /// Value type of iteration.
-    using value_type = std::tuple<IPRange const, PAYLOAD &>;
-    using pointer    = value_type *;
-    using reference  = value_type &;
-
-    /// Default constructor.
-    iterator() = default;
-
-    /// Copy constructor.
-    iterator(self_type const &that);
-
-    /// Assignment.
-    self_type &operator=(self_type const &that);
-
-    /// Pre-increment.
-    /// Move to the next element in the list.
-    /// @return The iterator.
-    self_type &operator++();
-
-    /// Pre-decrement.
-    /// Move to the previous element in the list.
-    /// @return The iterator.
-    self_type &operator--();
-
-    /// Post-increment.
-    /// Move to the next element in the list.
-    /// @return The iterator value before the increment.
-    self_type
-    operator++(int) {
-      self_type zret{*this};
-      ++*this;
-      return zret;
-    }
-
-    /// Post-decrement.
-    /// Move to the previous element in the list.
-    /// @return The iterator value before the decrement.
-    self_type
-    operator--(int) {
-      self_type zret{*this};
-      --*this;
-      return zret;
-    }
-
-    /// Dereference.
-    /// @return A reference to the referent.
-    value_type operator*() const;
-
-    /// Dereference.
-    /// @return A pointer to the referent.
-    value_type const *operator->() const;
-
-  protected:
-    using super_type::super_type; /// Inherit supertype constructors.
-  };
-
-  /** Find the payload for an @a addr.
-   *
-   * @param addr Address to find.
-   * @return Iterator for the range containing @a addr.
-   */
-  iterator
-  find(IPAddr const &addr) {
-    if (addr.is_ip4()) {
-      return this->find(addr.ip4());
-    } else if (addr.is_ip6()) {
-      return this->find(addr.ip6());
-    }
-    return this->end();
-  }
-
-  /** 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.
-   */
-  iterator
-  find(IP4Addr const &addr) {
-    auto spot = _ip4.find(addr);
-    return spot == _ip4.end() ? this->end() : iterator{_ip4.find(addr), _ip6.begin()};
-  }
-
-  /** 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.
-   */
-  iterator
-  find(IP6Addr const &addr) {
-    return {_ip4.end(), _ip6.find(addr)};
-  }
-
-  /// @return A constant iterator to the first element.
-  const_iterator begin() const;
-
-  /// @return A constent iterator past the last element.
-  const_iterator end() const;
-
-  iterator begin();
-
-  iterator end();
-
-  /// Iterator to the first IPv4 address.
-  const_iterator begin_ip4() const;
-  /// Iterator past the last IPv4 address.
-  const_iterator end_ip4() const;
-
-  const_iterator begin_ip6() const;
-  const_iterator end_ip6() const;
-
-  const_iterator
-  begin(sa_family_t family) const {
-    if (AF_INET == family) {
-      return this->begin_ip4();
-    } else if (AF_INET6 == family) {
-      return this->begin_ip6();
-    }
-    return this->end();
-  }
-
-  const_iterator
-  end(sa_family_t family) const {
-    if (AF_INET == family) {
-      return this->end_ip4();
-    } else if (AF_INET6 == family) {
-      return this->end_ip6();
-    }
-    return this->end();
-  }
-
-protected:
-  IP4Space _ip4; ///< Sub-space containing IPv4 ranges.
-  IP6Space _ip6; ///< sub-space containing IPv6 ranges.
-};
-
-template <typename PAYLOAD>
-IPSpace<PAYLOAD>::const_iterator::const_iterator(typename IP4Space::iterator const &iter4, typename IP6Space::iterator const &iter6)
-  : _iter_4(iter4), _iter_6(iter6) {
-  if (_iter_4.has_next()) {
-    new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
-  } else if (_iter_6.has_next()) {
-    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
-  }
-}
-
-template <typename PAYLOAD> IPSpace<PAYLOAD>::const_iterator::const_iterator(self_type const &that) {
-  *this = that;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator=(self_type const &that) -> self_type & {
-  _iter_4 = that._iter_4;
-  _iter_6 = that._iter_6;
-  new (&_value) value_type{that._value};
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator++() -> self_type & {
-  bool incr_p = false;
-  if (_iter_4.has_next()) {
-    ++_iter_4;
-    incr_p = true;
-    if (_iter_4.has_next()) {
-      new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
-      return *this;
-    }
-  }
-
-  if (_iter_6.has_next()) {
-    if (incr_p || (++_iter_6).has_next()) {
-      new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
-      return *this;
-    }
-  }
-  new (&_value) value_type{IPRange{}, *static_cast<PAYLOAD *>(detail::pseudo_nullptr)};
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator++(int) -> self_type {
-  self_type zret(*this);
-  ++*this;
-  return zret;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator--() -> self_type & {
-  if (_iter_6.has_prev()) {
-    --_iter_6;
-    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
-    return *this;
-  }
-  if (_iter_4.has_prev()) {
-    --_iter_4;
-    new (&_value) value_type{_iter_4->range(), _iter_4->payload()};
-    return *this;
-  }
-  new (&_value) value_type{IPRange{}, *static_cast<PAYLOAD *>(detail::pseudo_nullptr)};
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator--(int) -> self_type {
-  self_type zret(*this);
-  --*this;
-  return zret;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator*() const -> value_type {
-  return _value;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::const_iterator::operator->() const -> value_type const * {
-  return &_value;
-}
-
-/* Bit of subtlety with equality - although it seems that if @a _iter_4 is valid, it doesn't matter
- * where @a _iter6 is (because it is really the iterator location that's being checked), it's
- * neccesary to do the @a _iter_4 validity on both iterators to avoid the case of a false positive
- * where different internal iterators are valid. However, in practice the other (non-active)
- * iterator won't have an arbitrary value, it will be either @c begin or @c end in step with the
- * active iterator therefore it's effective and cheaper to just check both values.
- */
-
-template <typename PAYLOAD>
-bool
-IPSpace<PAYLOAD>::const_iterator::operator==(self_type const &that) const {
-  return _iter_4 == that._iter_4 && _iter_6 == that._iter_6;
-}
-
-template <typename PAYLOAD>
-bool
-IPSpace<PAYLOAD>::const_iterator::operator!=(self_type const &that) const {
-  return _iter_4 != that._iter_4 || _iter_6 != that._iter_6;
-}
-
-template <typename PAYLOAD> IPSpace<PAYLOAD>::iterator::iterator(self_type const &that) {
-  *this = that;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::iterator::operator=(self_type const &that) -> self_type & {
-  this->super_type::operator=(that);
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::iterator::operator->() const -> value_type const * {
-  return static_cast<value_type *>(&super_type::_value);
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::iterator::operator*() const -> value_type {
-  return reinterpret_cast<value_type const &>(super_type::_value);
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::iterator::operator++() -> self_type & {
-  this->super_type::operator++();
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::iterator::operator--() -> self_type & {
-  this->super_type::operator--();
-  return *this;
-}
-
-// --------------------------------------------------------------------------
-// -- IP4Addr --
-inline constexpr sa_family_t
-IP4Addr::family() {
-  return AF_value;
-}
-
-// -- IP6Addr --
-inline constexpr sa_family_t
-IP6Addr::family() {
-  return AF_value;
-}
-
-// -- IPAddr --
-// @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 IPAddr::IPAddr(in_addr_t addr) : _addr(addr), _family(IP4Addr::AF_value) {}
-
-inline IPAddr::IPAddr(in6_addr const &addr) : _addr(addr), _family(IP6Addr::AF_value) {}
-
-inline IPAddr::IPAddr(sockaddr const *addr) {
-  this->assign(addr);
-}
-
-inline IPAddr::IPAddr(IPEndpoint const &addr) {
-  this->assign(&addr.sa);
-}
-
-inline IPAddr::IPAddr(string_view const &text) {
-  this->load(text);
-}
-
-inline IPAddr &
-IPAddr::operator=(in_addr_t addr) {
-  _family    = AF_INET;
-  _addr._ip4 = addr;
-  return *this;
-}
-
-inline IPAddr &
-IPAddr::operator=(in6_addr const &addr) {
-  _family    = AF_INET6;
-  _addr._ip6 = addr;
-  return *this;
-}
-
-inline IPAddr &
-IPAddr::operator=(IPEndpoint const &addr) {
-  return this->assign(&addr.sa);
-}
-
-inline IPAddr &
-IPAddr::operator=(sockaddr const *addr) {
-  return this->assign(addr);
-}
-
-inline sa_family_t
-IPAddr::family() const {
-  return _family;
-}
-
-inline bool
-IPAddr::is_ip4() const {
-  return AF_INET == _family;
-}
-
-inline bool
-IPAddr::is_ip6() const {
-  return AF_INET6 == _family;
-}
-
-inline bool
-IPAddr::isCompatibleWith(self_type const &that) {
-  return this->is_valid() && _family == that._family;
-}
-
-inline bool
-IPAddr::is_loopback() const {
-  return (AF_INET == _family && _addr._ip4.is_loopback()) || (AF_INET6 == _family && _addr._ip6.is_loopback());
-}
-
-inline IPAddr &
-IPAddr::assign(in_addr_t addr) {
-  _family    = AF_INET;
-  _addr._ip4 = addr;
-  return *this;
-}
-
-inline IPAddr &
-IPAddr::assign(in6_addr const &addr) {
-  _family    = AF_INET6;
-  _addr._ip6 = addr;
-  return *this;
-}
-
-inline IPAddr &
-IPAddr::assign(sockaddr_in const *addr) {
-  if (addr) {
-    _family    = AF_INET;
-    _addr._ip4 = addr;
-  } else {
-    _family = AF_UNSPEC;
-  }
-  return *this;
-}
-
-inline IPAddr &
-IPAddr::assign(sockaddr_in6 const *addr) {
-  if (addr) {
-    _family    = AF_INET6;
-    _addr._ip6 = addr->sin6_addr;
-  } else {
-    _family = AF_UNSPEC;
-  }
-  return *this;
-}
-
-inline bool
-IPAddr::is_valid() const {
-  return _family == AF_INET || _family == AF_INET6;
-}
-
-inline IPAddr &
-IPAddr::invalidate() {
-  _family = AF_UNSPEC;
-  return *this;
-}
-
-// Associated operators.
-bool operator==(IPAddr const &lhs, sockaddr const *rhs);
-
-inline bool
-operator==(sockaddr const *lhs, IPAddr const &rhs) {
-  return rhs == lhs;
-}
-
-inline bool
-operator!=(IPAddr const &lhs, sockaddr const *rhs) {
-  return !(lhs == rhs);
-}
-
-inline bool
-operator!=(sockaddr const *lhs, IPAddr const &rhs) {
-  return !(rhs == lhs);
-}
+// --- Cross type address operators
 
 inline bool
 operator==(IPAddr const &lhs, IPEndpoint const &rhs) {
   return lhs == &rhs.sa;
 }
 
+/// Equality.
 inline bool
 operator==(IPEndpoint const &lhs, IPAddr const &rhs) {
   return &lhs.sa == rhs;
 }
 
+/// Inequality.
 inline bool
 operator!=(IPAddr const &lhs, IPEndpoint const &rhs) {
   return !(lhs == &rhs.sa);
 }
 
+/// Inequality.
 inline bool
 operator!=(IPEndpoint const &lhs, IPAddr const &rhs) {
   return !(rhs == &lhs.sa);
 }
 
-inline IP4Addr const &
-IPAddr::ip4() const {
-  return _addr._ip4;
-}
-
-inline IP6Addr const &
-IPAddr::ip6() const {
-  return _addr._ip6;
-}
-
-/// ------------------------------------------------------------------------------------
-
-inline IPEndpoint::IPEndpoint() {
-  sa.sa_family = AF_UNSPEC;
-}
-
-inline IPEndpoint::IPEndpoint(IPAddr const &addr) {
-  this->assign(addr);
-}
-
-inline IPEndpoint::IPEndpoint(sockaddr const *sa) {
-  this->assign(sa);
-}
-
-inline IPEndpoint::IPEndpoint(IPEndpoint::self_type const &that) : self_type(&that.sa) {}
-
-inline IPEndpoint &
-IPEndpoint::invalidate() {
-  sa.sa_family = AF_UNSPEC;
-  return *this;
-}
-
-inline void
-IPEndpoint::invalidate(sockaddr *addr) {
-  addr->sa_family = AF_UNSPEC;
-}
-
-inline bool
-IPEndpoint::is_valid() const {
-  return sa.sa_family == AF_INET || sa.sa_family == AF_INET6;
-}
-
-inline IPEndpoint &
-IPEndpoint::operator=(self_type const &that) {
-  self_type::assign(&sa, &that.sa);
-  return *this;
-}
-
-inline IPEndpoint &
-IPEndpoint::assign(sockaddr const *src) {
-  self_type::assign(&sa, src);
-  return *this;
-}
-
-inline IPEndpoint const &
-IPEndpoint::copy_to(sockaddr *addr) const {
-  self_type::assign(addr, &sa);
-  return *this;
-}
-
-inline bool
-IPEndpoint::is_ip4() const {
-  return AF_INET == sa.sa_family;
-}
-
-inline bool
-IPEndpoint::is_ip6() const {
-  return AF_INET6 == sa.sa_family;
-}
-
-inline sa_family_t
-IPEndpoint::family() const {
-  return sa.sa_family;
-}
-
-inline in_port_t &
-IPEndpoint::network_order_port() {
-  return self_type::port(&sa);
-}
-
-inline in_port_t
-IPEndpoint::network_order_port() const {
-  return self_type::port(&sa);
-}
-
-inline in_port_t
-IPEndpoint::host_order_port() const {
-  return ntohs(this->network_order_port());
-}
-
-inline in_port_t &
-IPEndpoint::port(sockaddr *sa) {
-  switch (sa->sa_family) {
-  case AF_INET:
-    return reinterpret_cast<sockaddr_in *>(sa)->sin_port;
-  case AF_INET6:
-    return reinterpret_cast<sockaddr_in6 *>(sa)->sin6_port;
-  }
-  // Force a failure upstream by returning a null reference.
-  throw std::domain_error("sockaddr is not a valid IP address");
-}
-
-inline in_port_t
-IPEndpoint::port(sockaddr const *sa) {
-  return self_type::port(const_cast<sockaddr *>(sa));
-}
-
-inline in_port_t
-IPEndpoint::host_order_port(sockaddr const *sa) {
-  return ntohs(self_type::port(sa));
-}
-
-// --- IPAddr variants ---
-
-inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {}
-
-inline IP4Addr::IP4Addr(std::string_view const &text) {
-  if (!this->load(text)) {
-    _addr = INADDR_ANY;
-  }
-}
-
-inline IP4Addr::IP4Addr(IPAddr const &addr) : _addr(addr._family == AF_INET ? addr._addr._ip4._addr : INADDR_ANY) {}
-
-inline IP4Addr &
-IP4Addr::operator<<=(unsigned n) {
-  _addr <<= n;
-  return *this;
-}
-
-inline IP4Addr &
-IP4Addr::operator>>=(unsigned n) {
-  _addr >>= n;
-  return *this;
-}
-
-inline IP4Addr &
-IP4Addr::operator&=(self_type const &that) {
-  _addr &= that._addr;
-  return *this;
-}
-
-inline IP4Addr &
-IP4Addr::operator|=(self_type const &that) {
-  _addr |= that._addr;
-  return *this;
-}
-
-inline IP4Addr &
-IP4Addr::operator++() {
-  ++_addr;
-  return *this;
-}
-
-inline IP4Addr &
-IP4Addr::operator--() {
-  --_addr;
-  return *this;
-}
-
-inline in_addr_t
-IP4Addr::network_order() const {
-  return htonl(_addr);
-}
-
-inline in_addr_t
-IP4Addr::host_order() const {
-  return _addr;
-}
-
-inline auto
-IP4Addr::operator=(in_addr_t ip) -> self_type & {
-  _addr = ntohl(ip);
-  return *this;
-}
-
-inline bool
-operator==(IP4Addr const &lhs, IP4Addr const &rhs) {
-  return lhs._addr == rhs._addr;
-}
-
-inline bool
-operator!=(IP4Addr const &lhs, IP4Addr const &rhs) {
-  return lhs._addr != rhs._addr;
-}
-
-inline bool
-operator<(IP4Addr const &lhs, IP4Addr const &rhs) {
-  return lhs._addr < rhs._addr;
-}
-
-inline bool
-operator<=(IP4Addr const &lhs, IP4Addr const &rhs) {
-  return lhs._addr <= rhs._addr;
-}
-
-inline bool
-operator>(IP4Addr const &lhs, IP4Addr const &rhs) {
-  return rhs < lhs;
-}
-
-inline bool
-operator>=(IP4Addr const &lhs, IP4Addr const &rhs) {
-  return rhs <= lhs;
-}
-
-inline IP4Addr &
-IP4Addr::operator&=(IPMask const &mask) {
-  _addr &= mask.as_ip4()._addr;
-  return *this;
-}
-
-inline IP4Addr &
-IP4Addr::operator|=(IPMask const &mask) {
-  _addr |= ~(mask.as_ip4()._addr);
-  return *this;
-}
-
-inline bool
-IP4Addr::is_any() const {
-  return _addr == INADDR_ANY;
-}
-
-inline bool
-IP4Addr::is_loopback() const {
-  return (*this)[3] == IN_LOOPBACKNET;
-}
-
-inline bool
-IP4Addr::is_multicast() const {
-  return IN_MULTICAST(_addr);
-}
-
-inline int
-IP4Addr::cmp(IP4Addr::self_type const &that) const {
-  return _addr < that._addr ? -1 : _addr > that._addr ? 1 : 0;
-}
-
-constexpr in_addr_t
-IP4Addr::reorder(in_addr_t src) {
-  return ((src & 0xFF) << 24) | (((src >> 8) & 0xFF) << 16) | (((src >> 16) & 0xFF) << 8) | ((src >> 24) & 0xFF);
-}
-
-// +++ IP6Addr +++
-
-inline IP6Addr::IP6Addr(in6_addr const &addr) {
-  *this = addr;
-}
-
-inline IP6Addr::IP6Addr(std::string_view const &text) {
-  if (!this->load(text)) {
-    this->clear();
-  }
-}
-
-inline IP6Addr::IP6Addr(IPAddr const &addr) : _addr{addr._addr._ip6._addr} {}
-
-inline bool
-IP6Addr::is_loopback() const {
-  return _addr._store[0] == 0 && _addr._store[1] == 1;
-}
-
-inline bool
-IP6Addr::is_multicast() const {
-  return _addr._raw[7] == 0xFF;
-}
-
-inline bool
-IP6Addr::is_any() const {
-  return _addr._store[0] == 0 && _addr._store[1] == 0;
-}
-
-inline bool
-IP6Addr::is_mapped_ipv4() const {
-  return 0 == _addr._store[0] && (_addr._quad[7] == 0 && _addr._quad[6] == 0xFFFF);
-}
-
-inline in6_addr &
-IP6Addr::copy_to(in6_addr &addr) const {
-  self_type::reorder(addr, _addr._raw);
-  return addr;
-}
-
-inline sockaddr *
-IP6Addr::copy_to(sockaddr *sa, in_port_t port) const {
-  IPEndpoint addr(sa);
-  self_type::reorder(addr.sa6.sin6_addr, _addr._raw);
-  addr.network_order_port() = port;
-  return sa;
-}
-
-inline in6_addr
-IP6Addr::network_order() const {
-  in6_addr zret;
-  return this->copy_to(zret);
-}
-
-inline auto
-IP6Addr::clear() -> self_type & {
-  _addr._store[0] = _addr._store[1] = 0;
-  return *this;
-}
-
-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) {
-    *this = addr->sin6_addr;
-  } else {
-    this->clear();
-  }
-  return *this;
-}
-
-inline IP6Addr &
-IP6Addr::operator++() {
-  if (++(_addr._store[1]) == 0) {
-    ++(_addr._store[0]);
-  }
-  return *this;
-}
-
-inline IP6Addr &
-IP6Addr::operator--() {
-  if (--(_addr._store[1]) == ~static_cast<uint64_t>(0)) {
-    --(_addr._store[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._store[0] == rhs._addr._store[0] && lhs._addr._store[1] == rhs._addr._store[1];
-}
-
-inline bool
-operator!=(IP6Addr const &lhs, IP6Addr const &rhs) {
-  return lhs._addr._store[0] != rhs._addr._store[0] || lhs._addr._store[1] != rhs._addr._store[1];
-}
-
-inline bool
-operator<(IP6Addr const &lhs, IP6Addr const &rhs) {
-  return lhs._addr._store[0] < rhs._addr._store[0] ||
-         (lhs._addr._store[0] == rhs._addr._store[0] && lhs._addr._store[1] < rhs._addr._store[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._store[0] < rhs._addr._store[0] ||
-         (lhs._addr._store[0] == rhs._addr._store[0] && lhs._addr._store[1] <= rhs._addr._store[1]);
-}
-
-inline bool
-operator>=(IP6Addr const &lhs, IP6Addr const &rhs) {
-  return rhs <= lhs;
-}
-
-inline IP6Addr &
-IP6Addr::operator&=(IPMask const &mask) {
-  if (mask._cidr < WORD_WIDTH) {
-    _addr._store[MSW] &= (~word_type{0} << (WORD_WIDTH - mask._cidr));
-    _addr._store[LSW] = 0;
-  } else if (mask._cidr < WIDTH) {
-    _addr._store[LSW] &= (~word_type{0} << (2 * WORD_WIDTH - mask._cidr));
-  }
-  return *this;
-}
-
-inline IP6Addr &
-IP6Addr::operator|=(IPMask const &mask) {
-  if (mask._cidr < WORD_WIDTH) {
-    _addr._store[MSW] |= (~word_type{0} >> mask._cidr);
-    _addr._store[LSW] = ~word_type{0};
-  } else if (mask._cidr < WIDTH) {
-    _addr._store[LSW] |= (~word_type{0} >> (mask._cidr - WORD_WIDTH));
-  }
-  return *this;
-}
-
-// --- //
-
-inline bool
-IPAddr::operator==(self_type const &that) const {
-  switch (_family) {
-  case AF_INET:
-    return that._family == AF_INET && _addr._ip4 == that._addr._ip4;
-  case AF_INET6:
-    return that._family == AF_INET6 && _addr._ip6 == that._addr._ip6;
-  default:
-    return ! that.is_valid();
-  }
-}
-
-inline bool
-IPAddr::operator!=(self_type const &that) const {
-  return !(*this == that);
-}
-
-inline bool
-IPAddr::operator>(self_type const &that) const {
-  return that < *this;
-}
-
-inline bool
-IPAddr::operator<=(self_type const &that) const {
-  return !(that < *this);
-}
-
-inline bool
-IPAddr::operator>=(self_type const &that) const {
-  return !(*this < that);
-}
-
-// Disambiguating comparisons.
-
-inline bool
-operator==(IPAddr const &lhs, IP4Addr const &rhs) {
-  return lhs.is_ip4() && static_cast<IP4Addr const &>(lhs) == rhs;
-}
-
-inline bool
-operator!=(IPAddr const &lhs, IP4Addr const &rhs) {
-  return !lhs.is_ip4() || static_cast<IP4Addr const &>(lhs) != rhs;
-}
-
-inline bool
-operator==(IP4Addr const &lhs, IPAddr const &rhs) {
-  return rhs.is_ip4() && lhs == static_cast<IP4Addr const &>(rhs);
-}
-
-inline bool
-operator!=(IP4Addr const &lhs, IPAddr const &rhs) {
-  return !rhs.is_ip4() || lhs != static_cast<IP4Addr const &>(rhs);
-}
-
-inline bool
-operator==(IPAddr const &lhs, IP6Addr const &rhs) {
-  return lhs.is_ip6() && static_cast<IP6Addr const &>(lhs) == rhs;
-}
-
-inline bool
-operator!=(IPAddr const &lhs, IP6Addr const &rhs) {
-  return !lhs.is_ip6() || static_cast<IP6Addr const &>(lhs) != rhs;
-}
-
-inline bool
-operator==(IP6Addr const &lhs, IPAddr const &rhs) {
-  return rhs.is_ip6() && lhs == rhs.ip6();
-}
-
-inline bool
-operator!=(IP6Addr const &lhs, IPAddr const &rhs) {
-  return !rhs.is_ip6() || lhs != rhs.ip6();
-}
-
-// +++ IPRange +++
-
-inline IP4Range::IP4Range(string_view const &text) {
-  this->load(text);
-}
-
-inline auto
-IP4Range::networks() const -> NetSource {
-  return {NetSource{*this}};
-}
-
-inline IP6Range::IP6Range(string_view const &text) {
-  this->load(text);
-}
-
-inline auto
-IP6Range::networks() const -> NetSource {
-  return {NetSource{*this}};
-}
-
-inline IPRange::IPRange(IP4Range const &range) : _family(AF_INET) {
-  _range._ip4 = range;
-}
-
-inline IPRange::IPRange(IP6Range const &range) : _family(AF_INET6) {
-  _range._ip6 = range;
-}
-
-inline IPRange::IPRange(string_view const &text) {
-  this->load(text);
-}
-
-inline auto
-IPRange::networks() const -> NetSource {
-  return {NetSource{*this}};
-}
-
-inline bool
-IPRange::is(sa_family_t family) const {
-  return family == _family;
-}
-
-// +++ IPMask +++
-
-inline IPMask::IPMask(raw_type width) : _cidr(width) {}
-
-inline bool
-IPMask::is_valid() const {
-  return _cidr < INVALID;
-}
-
-inline auto
-IPMask::width() const -> raw_type {
-  return _cidr;
-}
-
-inline bool
-operator==(IPMask const &lhs, IPMask const &rhs) {
-  return lhs.width() == rhs.width();
-}
-
-inline bool
-operator!=(IPMask const &lhs, IPMask const &rhs) {
-  return lhs.width() != rhs.width();
-}
-
-inline bool
-operator<(IPMask const &lhs, IPMask const &rhs) {
-  return lhs.width() < rhs.width();
-}
-
-inline IP4Addr
-IPMask::as_ip4() const {
-  static constexpr auto MASK = ~in_addr_t{0};
-  in_addr_t addr             = MASK;
-  if (_cidr < IP4Addr::WIDTH) {
-    addr <<= IP4Addr::WIDTH - _cidr;
-  }
-  return IP4Addr{addr};
-}
-
-inline auto
-IPMask::clear() ->  self_type & {
-  _cidr = INVALID;
-  return *this;
-}
-
-inline auto
-IPMask::operator<<=(raw_type n) -> self_type & {
-  _cidr -= n;
-  return *this;
-}
-
-inline auto
-IPMask::operator>>=(IPMask::raw_type n) -> self_type & {
-  _cidr += n;
-  return *this;
-}
-
-// +++ mixed mask operators +++
-
-inline IP4Addr
-operator&(IP4Addr const &addr, IPMask const &mask) {
-  return IP4Addr{addr} &= mask;
-}
-
-inline IP4Addr
-operator|(IP4Addr const &addr, IPMask const &mask) {
-  return IP4Addr{addr} |= mask;
-}
-
-inline IP6Addr
-operator&(IP6Addr const &addr, IPMask const &mask) {
-  return IP6Addr{addr} &= mask;
-}
-
-inline IP6Addr
-operator|(IP6Addr const &addr, IPMask const &mask) {
-  return IP6Addr{addr} |= mask;
-}
-
-inline IPAddr
-operator&(IPAddr const &addr, IPMask const &mask) {
-  return IPAddr{addr} &= mask;
-}
-
-inline IPAddr
-operator|(IPAddr const &addr, IPMask const &mask) {
-  return IPAddr{addr} |= mask;
-}
-
-// +++ IPNet +++
-
-inline IP4Net::IP4Net(swoc::IP4Addr addr, swoc::IPMask mask) : _addr(addr & mask), _mask(mask) {}
-
-inline IPMask const &
-IP4Net::mask() const {
-  return _mask;
-}
-
-inline bool
-IP4Net::is_valid() const {
-  return _mask.is_valid();
-}
-
-inline IP4Addr
-IP4Net::lower_bound() const {
-  return _addr;
-}
-
-inline IP4Addr
-IP4Net::upper_bound() const {
-  return _addr | _mask;
-}
-
-inline IP4Range
-IP4Net::as_range() const {
-  return {this->lower_bound(), this->upper_bound()};
-}
-
-inline bool
-IP4Net::operator==(self_type const &that) const {
-  return _mask == that._mask && _addr == that._addr;
-}
-
-inline bool
-IP4Net::operator!=(self_type const &that) const {
-  return _mask != that._mask || _addr != that._addr;
-}
-
-inline IP4Net::self_type &
-IP4Net::assign(IP4Addr const &addr, IPMask const &mask) {
-  _addr = addr & mask;
-  _mask = mask;
-  return *this;
-}
-
-inline IP6Net::IP6Net(swoc::IP6Addr addr, swoc::IPMask mask) : _addr(addr & mask), _mask(mask) {}
-
-inline IPMask const &
-IP6Net::mask() const {
-  return _mask;
-}
-
-inline bool
-IP6Net::is_valid() const {
-  return _mask.is_valid();
-}
-
-inline IP6Addr
-IP6Net::lower_bound() const {
-  return _addr;
-}
-
-inline IP6Addr
-IP6Net::upper_bound() const {
-  return _addr | _mask;
-}
-
-inline IP6Range
-IP6Net::as_range() const {
-  return {this->lower_bound(), this->upper_bound()};
-}
-
-inline bool
-IP6Net::operator==(self_type const &that) const {
-  return _mask == that._mask && _addr == that._addr;
-}
-
-inline bool
-IP6Net::operator!=(self_type const &that) const {
-  return _mask != that._mask || _addr != that._addr;
-}
-
-inline IP6Net::self_type &
-IP6Net::assign(IP6Addr const &addr, IPMask const &mask) {
-  _addr = addr & mask;
-  _mask = mask;
-  return *this;
-}
-
-inline IPNet::IPNet(IPAddr const &addr, IPMask const &mask) : _addr(addr & mask), _mask(mask) {}
-
-inline IPNet::IPNet(TextView text) {
-  this->load(text);
-}
-
-inline bool
-IPNet::is_valid() const {
-  return _mask.is_valid();
-}
-
-inline IPAddr
-IPNet::lower_bound() const {
-  return _addr;
-}
-
-inline IPAddr
-IPNet::upper_bound() const {
-  return _addr | _mask;
-}
-
-inline IPMask::raw_type
-IPNet::width() const {
-  return _mask.width();
-}
-
-inline IPMask const &
-IPNet::mask() const {
-  return _mask;
-}
-
-inline IPRange
-IPNet::as_range() const {
-  return {this->lower_bound(), this->upper_bound()};
-}
-
-inline IPNet::self_type &
-IPNet::assign(IPAddr const &addr, IPMask const &mask) {
-  _addr = addr & mask;
-  _mask = mask;
-  return *this;
-}
-
-inline bool
-IPNet::operator==(IPNet::self_type const &that) const {
-  return _mask == that._mask && _addr == that._addr;
-}
-
-inline bool
-IPNet::operator!=(IPNet::self_type const &that) const {
-  return _mask != that._mask || _addr != that._addr;
-}
-
-inline bool
-operator==(IPNet const &lhs, IP4Net const &rhs) {
-  return lhs.is_ip4() && lhs.ip4() == rhs;
-}
-
-inline bool
-operator==(IP4Net const &lhs, IPNet const &rhs) {
-  return rhs.is_ip4() && rhs.ip4() == lhs;
-}
-
-inline bool
-operator==(IPNet const &lhs, IP6Net const &rhs) {
-  return lhs.is_ip6() && lhs.ip6() == rhs;
-}
-
-inline bool
-operator==(IP6Net const &lhs, IPNet const &rhs) {
-  return rhs.is_ip6() && rhs.ip6() == lhs;
-}
-
-// +++ Range -> Network classes +++
-
-inline bool
-IP4Range::NetSource::is_valid(swoc::IP4Addr mask) const {
-  return ((mask._addr & _range._min._addr) == _range._min._addr) && ((_range._min._addr | ~mask._addr) <= _range._max._addr);
-}
-
-inline IP4Net
-IP4Range::NetSource::operator*() const {
-  return IP4Net{_range.min(), IPMask{_cidr}};
-}
-
-inline IP4Range::NetSource::iterator
-IP4Range::NetSource::begin() const {
-  return *this;
-}
-
-inline IP4Range::NetSource::iterator
-IP4Range::NetSource::end() {
-  return self_type{range_type{}};
-}
-
-inline bool
-IP4Range::NetSource::empty() const {
-  return _range.empty();
-}
-
-inline IPMask
-IP4Range::NetSource::mask() const {
-  return IPMask{_cidr};
-}
-
-inline auto
-IP4Range::NetSource::operator->() -> self_type * {
-  return this;
-}
-
-inline IP4Addr const &
-IP4Range::NetSource::addr() const {
-  return _range.min();
-}
-
-inline bool
-IP4Range::NetSource::operator==(IP4Range::NetSource::self_type const &that) const {
-  return ((_cidr == that._cidr) && (_range == that._range)) || (_range.empty() && that._range.empty());
-}
-
-inline bool
-IP4Range::NetSource::operator!=(IP4Range::NetSource::self_type const &that) const {
-  return !(*this == that);
-}
-
-inline auto
-IP6Range::NetSource::begin() const -> iterator {
-  return *this;
-}
-
-inline auto
-IP6Range::NetSource::end() const -> iterator {
-  return self_type{range_type{}};
-}
-
-inline bool
-IP6Range::NetSource::empty() const {
-  return _range.empty();
-}
-
-inline IP6Net
-IP6Range::NetSource::operator*() const {
-  return IP6Net{_range.min(), _mask};
-}
-
-inline auto
-IP6Range::NetSource::operator->() -> self_type * {
-  return this;
-}
-
-inline bool
-IP6Range::NetSource::is_valid(IPMask const &mask) {
-  return ((_range.min() & mask) == _range.min()) && ((_range.min() | mask) <= _range.max());
-}
-
-inline bool
-IP6Range::NetSource::operator==(IP6Range::NetSource::self_type const &that) const {
-  return ((_mask == that._mask) && (_range == that._range)) || (_range.empty() && that._range.empty());
-}
-
-inline bool
-IP6Range::NetSource::operator!=(IP6Range::NetSource::self_type const &that) const {
-  return !(*this == that);
-}
-
-inline IP6Addr const &
-IP6Range::NetSource::addr() const {
-  return _range.min();
-}
-
-inline IPMask
-IP6Range::NetSource::mask() const {
-  return _mask;
-}
-
-inline IPRange::NetSource::NetSource(IPRange::NetSource::range_type const &range) {
-  if (range.is_ip4()) {
-    new (&_ip4) decltype(_ip4)(range.ip4());
-    _family = AF_INET;
-  } else if (range.is_ip6()) {
-    new (&_ip6) decltype(_ip6)(range.ip6());
-    _family = AF_INET6;
-  }
-}
-
-inline auto
-IPRange::NetSource::begin() const -> iterator {
-  return *this;
-}
-
-inline auto
-IPRange::NetSource::end() const -> iterator {
-  return AF_INET == _family ? self_type{IP4Range{}} : AF_INET6 == _family ? self_type{IP6Range{}} : self_type{IPRange{}};
-}
-
-inline IPAddr
-IPRange::NetSource::addr() const {
-  if (AF_INET == _family) {
-    return _ip4.addr();
-  } else if (AF_INET6 == _family) {
-    return _ip6.addr();
-  }
-  return {};
-}
-
-inline IPMask
-IPRange::NetSource::mask() const {
-  if (AF_INET == _family) {
-    return _ip4.mask();
-  } else if (AF_INET6 == _family) {
-    return _ip6.mask();
-  }
-  return {};
-}
-
-inline IPNet
-IPRange::NetSource::operator*() const {
-  return {this->addr(), this->mask()};
-}
-
-inline auto
-IPRange::NetSource::operator++() -> self_type & {
-  if (AF_INET == _family) {
-    ++_ip4;
-  } else if (AF_INET6 == _family) {
-    ++_ip6;
-  }
-  return *this;
-}
-
-inline auto
-IPRange::NetSource::operator->() -> self_type * {
-  return this;
-}
-
-inline bool
-IPRange::NetSource::operator==(self_type const &that) const {
-  if (_family != that._family) {
-    return false;
-  }
-  if (AF_INET == _family) {
-    return _ip4 == that._ip4;
-  } else if (AF_INET6 == _family) {
-    return _ip6 == that._ip6;
-  } else if (AF_UNSPEC == _family) {
-    return true;
-  }
-  return false;
-}
-
-inline bool
-IPRange::NetSource::operator!=(self_type const &that) const {
-  return !(*this == that);
-}
-
-// --- IPSpace
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::mark(IPRange const &range, PAYLOAD const &payload) -> self_type & {
-  if (range.is(AF_INET)) {
-    _ip4.mark(range.ip4(), payload);
-  } else if (range.is(AF_INET6)) {
-    _ip6.mark(range.ip6(), payload);
-  }
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::fill(IPRange const &range, PAYLOAD const &payload) -> self_type & {
-  if (range.is(AF_INET6)) {
-    _ip6.fill(range.ip6(), payload);
-  } else if (range.is(AF_INET)) {
-    _ip4.fill(range.ip4(), payload);
-  }
-  return *this;
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::erase(IPRange const &range) -> self_type & {
-  if (range.is(AF_INET)) {
-    _ip4.erase(range.ip4());
-  } else if (range.is(AF_INET6)) {
-    _ip6.erase(range.ip6());
-  }
-  return *this;
-}
-
-template <typename PAYLOAD>
-template <typename F, typename U>
-auto
-IPSpace<PAYLOAD>::blend(IPRange const &range, U const &color, F &&blender) -> self_type & {
-  if (range.is(AF_INET)) {
-    _ip4.blend(range.ip4(), color, blender);
-  } else if (range.is(AF_INET6)) {
-    _ip6.blend(range.ip6(), color, blender);
-  }
-  return *this;
-}
-
-template <typename PAYLOAD>
-template <typename F, typename U>
-auto
-IPSpace<PAYLOAD>::blend(IP4Range const &range, U const &color, F &&blender) -> self_type & {
-  _ip4.blend(range, color, std::forward<F>(blender));
-  return *this;
-}
-
-template <typename PAYLOAD>
-template <typename F, typename U>
-auto
-IPSpace<PAYLOAD>::blend(IP6Range const &range, U const &color, F &&blender) -> self_type & {
-  _ip6.blend(range, color, std::forward<F>(blender));
-  return *this;
-}
-
-template <typename PAYLOAD>
-void
-IPSpace<PAYLOAD>::clear() {
-  _ip4.clear();
-  _ip6.clear();
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::begin() const -> const_iterator {
-  auto nc_this = const_cast<self_type *>(this);
-  return const_iterator(nc_this->_ip4.begin(), nc_this->_ip6.begin());
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::end() const -> const_iterator {
-  auto nc_this = const_cast<self_type *>(this);
-  return const_iterator(nc_this->_ip4.end(), nc_this->_ip6.end());
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::begin_ip4() const -> const_iterator {
-  return this->begin();
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::end_ip4() const -> const_iterator {
-  auto nc_this = const_cast<self_type *>(this);
-  return { nc_this->_ip4.end(), nc_this->_ip6.begin() };
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::begin_ip6() const -> const_iterator {
-  auto nc_this = const_cast<self_type *>(this);
-  return { nc_this->_ip4.end(), nc_this->_ip6.begin() };
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::end_ip6() const -> const_iterator {
-  return this->end();
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::begin() -> iterator {
-  return iterator{_ip4.begin(), _ip6.begin()};
-}
-
-template <typename PAYLOAD>
-auto
-IPSpace<PAYLOAD>::end() -> iterator {
-  return iterator{_ip4.end(), _ip6.end()};
-}
-
-template <typename PAYLOAD>
-size_t
-IPSpace<PAYLOAD>::count(sa_family_t f) const {
-  return IP4Addr::AF_value == f ? _ip4.count() : IP6Addr::AF_value == f ? _ip6.count() : 0;
-}
-
 }} // namespace swoc::SWOC_VERSION_NS
-
-/// @cond NOT_DOCUMENTED
-namespace std {
-
-// -- Tuple support for IP4Net --
-template <> class tuple_size<swoc::IP4Net> : public std::integral_constant<size_t, 2> {};
-
-template <size_t IDX> class tuple_element<IDX, swoc::IP4Net> { static_assert("swoc::IP4Net tuple index out of range"); };
-
-template <> class tuple_element<0, swoc::IP4Net> {
-public:
-  using type = swoc::IP4Addr;
-};
-
-template <> class tuple_element<1, swoc::IP4Net> {
-public:
-  using type = swoc::IPMask;
-};
-
-// -- Tuple support for IP6Net --
-template <> class tuple_size<swoc::IP6Net> : public std::integral_constant<size_t, 2> {};
-
-template <size_t IDX> class tuple_element<IDX, swoc::IP6Net> { static_assert("swoc::IP6Net tuple index out of range"); };
-
-template <> class tuple_element<0, swoc::IP6Net> {
-public:
-  using type = swoc::IP6Addr;
-};
-
-template <> class tuple_element<1, swoc::IP6Net> {
-public:
-  using type = swoc::IPMask;
-};
-
-// -- Tuple support for IPNet --
-template <> class tuple_size<swoc::IPNet> : public std::integral_constant<size_t, 2> {};
-
-template <size_t IDX> class tuple_element<IDX, swoc::IPNet> { static_assert("swoc::IPNet tuple index out of range"); };
-
-template <> class tuple_element<0, swoc::IPNet> {
-public:
-  using type = swoc::IPAddr;
-};
-
-template <> class tuple_element<1, swoc::IPNet> {
-public:
-  using type = swoc::IPMask;
-};
-
-} // namespace std
-/// @endcond
-
-namespace swoc { inline namespace SWOC_VERSION_NS {
-
-template <size_t IDX>
-typename std::tuple_element<IDX, IP4Net>::type
-get(swoc::IP4Net const &net) {
-  if constexpr (IDX == 0) {
-    return net.lower_bound();
-  } else if constexpr (IDX == 1) {
-    return net.mask();
-  }
-}
-
-template <size_t IDX>
-typename std::tuple_element<IDX, IP6Net>::type
-get(swoc::IP6Net const &net) {
-  if constexpr (IDX == 0) {
-    return net.lower_bound();
-  } else if constexpr (IDX == 1) {
-    return net.mask();
-  }
-}
-
-template <size_t IDX>
-typename std::tuple_element<IDX, IPNet>::type
-get(swoc::IPNet const &net) {
-  if constexpr (IDX == 0) {
-    return net.lower_bound();
-  } else if constexpr (IDX == 1) {
-    return net.mask();
-  }
-}
-
-}} // namespace swoc::SWOC_VERSION_NS
-
-namespace std {
-template <> struct hash<swoc::IP4Addr> {
-  uint32_t operator()(swoc::IP4Addr const &addr) const {
-    return addr.network_order();
-  }
-};
-
-template <> struct hash<swoc::IP6Addr> {
-  uint32_t operator()(swoc::IP6Addr const &addr) const {
-    // XOR the 64 chunks then XOR that down to 32 bits.
-    auto words = addr.as_span<uint64_t>();
-    union {
-      uint64_t w;
-      uint32_t n[2];
-    } x{words[0] ^ words[1]};
-    return x.n[0] ^ x.n[1];
-  }
-};
-
-template <> struct hash<swoc::IPAddr> {
-  uint32_t operator()(swoc::IPAddr const &addr) const {
-    return addr.is_ip4() ? hash<swoc::IP4Addr>()(addr.ip4()) : addr.is_ip6() ? hash<swoc::IP6Addr>()(addr.ip6()) : 0;
-  }
-};
-} // namespace std
diff --git a/lib/swoc/include/swoc/swoc_meta.h b/lib/swoc/include/swoc/swoc_meta.h
index 0ed41b7..959f68d 100644
--- a/lib/swoc/include/swoc/swoc_meta.h
+++ b/lib/swoc/include/swoc/swoc_meta.h
@@ -65,18 +65,18 @@
 template <unsigned N> struct CaseTag : /** @cond DOXYGEN_FAIL */ public CaseTag<N - 1> /** @endcond */ {
   constexpr CaseTag() {}
 
-  static constexpr unsigned value = N;
+  static constexpr unsigned value = N; ///< Case tag value.
 };
 
-/// Anchor the hierarchy.
+/// Case hierarchy anchor.
 template <> struct CaseTag<0> {
   constexpr CaseTag() {}
 
-  static constexpr unsigned value = 0;
+  static constexpr unsigned value = 0; ///< Case tag value.
 };
 
-/** This is the final case - it forces the super class hierarchy.
- * After defining the cases using the indexed case arguments, this is used to to perform the call.
+/** This is the final case - it forces the class hierarchy.
+ * After defining the cases using the indexed case arguments, this is used to perform the call.
  * To increase the hierarchy depth, change the template argument to a larger number.
  */
 static constexpr CaseTag<9> CaseArg{};
diff --git a/lib/swoc/include/swoc/swoc_version.h b/lib/swoc/include/swoc/swoc_version.h
index c39d785..ee9d56a 100644
--- a/lib/swoc/include/swoc/swoc_version.h
+++ b/lib/swoc/include/swoc/swoc_version.h
@@ -23,11 +23,11 @@
 #pragma once
 
 #if !defined(SWOC_VERSION_NS)
-#define SWOC_VERSION_NS _1_3_8
+#define SWOC_VERSION_NS _1_4_0
 #endif
 
 namespace swoc { inline namespace SWOC_VERSION_NS {
 static constexpr unsigned MAJOR_VERSION = 1;
-static constexpr unsigned MINOR_VERSION = 3;
-static constexpr unsigned POINT_VERSION = 8;
+static constexpr unsigned MINOR_VERSION = 4;
+static constexpr unsigned POINT_VERSION = 0;
 }} // namespace swoc::SWOC_VERSION_NS
diff --git a/lib/swoc/src/Errata.cc b/lib/swoc/src/Errata.cc
index 08f1e55..4033009 100644
--- a/lib/swoc/src/Errata.cc
+++ b/lib/swoc/src/Errata.cc
@@ -27,10 +27,6 @@
 
 std::string_view Errata::DEFAULT_GLUE{"\n", 1};
 
-/** This is the implementation class for Errata.
-
-    It holds the actual messages and is treated as a passive data object with nice constructors.
-*/
 string_view
 Errata::Data::localize(string_view src) {
   auto span = _arena.alloc(src.size());
@@ -45,8 +41,8 @@
 Errata::Severity Errata::FAILURE_SEVERITY(2);
 Errata::Severity Errata::FILTER_SEVERITY(0);
 
-// Provide a somewhat reasonable set of default severities and names
-std::array<swoc::TextView, 4> Severity_Names{{"Info", "Warning", "Error"}};
+/// Default set of severity names.
+std::array<swoc::TextView, 3> Severity_Names{{"Info", "Warning", "Error"}};
 
 swoc::MemSpan<TextView const> Errata::SEVERITY_NAMES{Severity_Names.data(), Severity_Names.size()};
 
diff --git a/lib/swoc/src/bw_format.cc b/lib/swoc/src/bw_format.cc
index f22a522..ac9ce9e 100644
--- a/lib/swoc/src/bw_format.cc
+++ b/lib/swoc/src/bw_format.cc
@@ -649,6 +649,30 @@
 }
 
 BufferWriter &
+bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) {
+  using namespace swoc::literals;
+  bwf::Spec ptr_spec{spec};
+  ptr_spec._radix_lead_p = true;
+
+  if (ptr == nullptr) {
+    if (spec._type == 's' || spec._type == 'S') {
+      ptr_spec._type = bwf::Spec::DEFAULT_TYPE;
+      ptr_spec._ext  = ""_sv; // clear any extension.
+      return bwformat(w, spec, spec._type == 's' ? "null"_sv : "NULL"_sv);
+    } else if (spec._type == bwf::Spec::DEFAULT_TYPE) {
+      return w; // print nothing if there is no format character override.
+    }
+  }
+
+  if (ptr_spec._type == bwf::Spec::DEFAULT_TYPE || ptr_spec._type == 'p') {
+    ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex.
+  } else if (ptr_spec._type == 'P') {
+    ptr_spec._type = 'X'; // P means upper hex, overriding other specializations.
+  }
+  return bwf::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false);
+}
+
+BufferWriter &
 bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex) {
   char fmt_type      = spec._type;
   const char *digits = bwf::UPPER_DIGITS;
@@ -672,7 +696,7 @@
 BufferWriter &
 bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void> const &span) {
   if ('x' == spec._type || 'X' == spec._type) {
-    const char *digits = 'X' == spec._type ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS;
+    const char *digits =  ('X' == spec._type) ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS;
     size_t block       = spec._prec > 0 ? spec._prec : span.size();
     TextView view{span.view()};
     bool space_p = false;
@@ -919,67 +943,6 @@
   return w;
 }
 
-// --- IP address support ---
-
-#if 0
-BufferWriter &
-bwformat(BufferWriter &w, bwf::Spec const &spec, IpAddr const &addr)
-{
-  bwf::Spec local_spec{spec}; // Format for address elements and port.
-  bool addr_p{true};
-  bool family_p{false};
-
-  if (spec._ext.size()) {
-    if (spec._ext.front() == '=') {
-      local_spec._ext.remove_prefix(1);
-    } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
-      local_spec._ext.remove_prefix(2);
-    }
-  }
-  if (local_spec._ext.size()) {
-    addr_p = false;
-    for (char c : local_spec._ext) {
-      switch (c) {
-        case 'a':
-        case 'A':
-          addr_p = true;
-          break;
-        case 'f':
-        case 'F':
-          family_p = true;
-          break;
-      }
-    }
-  }
-
-  if (addr_p) {
-    if (addr.isIp4()) {
-      bwformat(w, spec, addr._addr._ip4);
-    } else if (addr.isIp6()) {
-      bwformat(w, spec, addr._addr._ip6);
-    } else {
-      w.print("*Not IP address [{}]*", addr.family());
-    }
-  }
-
-  if (family_p) {
-    local_spec._min = 0;
-    if (addr_p) {
-      w.write(' ');
-    }
-    if (spec.has_numeric_type()) {
-      bwformat(w, local_spec, static_cast<uintmax_t>(addr.family()));
-    } else {
-      bwformat(w, local_spec, addr.family());
-    }
-  }
-  return w;
-}
-#endif
-
-namespace {
-} // namespace
-
 }} // namespace swoc::SWOC_VERSION_NS
 
 namespace std {
diff --git a/lib/swoc/src/bw_ip_format.cc b/lib/swoc/src/bw_ip_format.cc
index 1c96b53..ecef729 100644
--- a/lib/swoc/src/bw_ip_format.cc
+++ b/lib/swoc/src/bw_ip_format.cc
@@ -238,7 +238,7 @@
 
   if (addr_p) {
     if (addr.is_ip4()) {
-      swoc::bwformat(w, spec, static_cast<IP4Addr const &>(addr));
+      swoc::bwformat(w, spec, addr.ip4());
     } else if (addr.is_ip6()) {
       swoc::bwformat(w, spec, addr.ip6().network_order());
     } else {
diff --git a/lib/swoc/src/string_view_util.cc b/lib/swoc/src/string_view_util.cc
new file mode 100644
index 0000000..3c483ca
--- /dev/null
+++ b/lib/swoc/src/string_view_util.cc
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Apache Software Foundation 2019
+/** @file
+
+    Additional handy utilities for @c string_view and hence also @c TextView.
+*/
+
+#include "swoc/string_view_util.h"
+
+int
+memcmp(std::string_view const &lhs, std::string_view const &rhs) {
+  int zret = 0;
+  size_t n = rhs.size();
+
+  // Seems a bit ugly but size comparisons must be done anyway to get the memcmp args.
+  if (lhs.size() < rhs.size()) {
+    zret = 1;
+    n    = lhs.size();
+  } else if (lhs.size() > rhs.size()) {
+    zret = -1;
+  } else if (lhs.data() == rhs.data()) { // same memory, obviously equal.
+    return 0;
+  }
+
+  int r = ::memcmp(lhs.data(), rhs.data(), n);
+  return r ? r : zret;
+}
+
+int
+strcasecmp(const std::string_view &lhs, const std::string_view &rhs) {
+  int zret = 0;
+  size_t n = rhs.size();
+
+  // Seems a bit ugly but size comparisons must be done anyway to get the @c strncasecmp args.
+  if (lhs.size() < rhs.size()) {
+    zret = 1;
+    n    = lhs.size();
+  } else if (lhs.size() > rhs.size()) {
+    zret = -1;
+  } else if (lhs.data() == rhs.data()) { // the same memory, obviously equal.
+    return 0;
+  }
+
+  int r = ::strncasecmp(lhs.data(), rhs.data(), n);
+
+  return r ? r : zret;
+}
diff --git a/lib/swoc/src/swoc_ip.cc b/lib/swoc/src/swoc_ip.cc
index dc39df5..9434221 100644
--- a/lib/swoc/src/swoc_ip.cc
+++ b/lib/swoc/src/swoc_ip.cc
@@ -258,26 +258,42 @@
   TextView src{text};
   int n = SIZE; /// # of octets
 
-  if (src.empty() || ('[' == *src && ((++src).empty() || src.back() != ']'))) {
+  _addr = INADDR_ANY; // clear to zero.
+
+  // empty or trailing dot or empty brackets or unmatched brackets.
+  if (src.empty() || src.back() == '.' || ('[' == *src && ((++src).empty() || src.back() != ']'))) {
     return false;
   }
 
-  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() && x <= std::numeric_limits<uint8_t>::max()) {
-      octet[--n] = x;
-    } else {
+  in_addr_t max = std::numeric_limits<in_addr_t>::max();
+  while (n > 0) {
+    TextView parsed;
+    auto token = src.take_prefix_at('.');
+    auto v = svtou(token, &parsed);
+    if (parsed.size() != token.size()) {
       break;
     }
+    if (src.empty()) {
+      if (v <= max) {
+        _addr += v;
+        n = 0; // signal complete.
+      }
+      break;
+    } else if (v <= std::numeric_limits<uint8_t>::max()){
+      _addr += v << ( --n * 8);
+    } else {
+      break; // invalid.
+    }
+    max >>= 8; // reduce by one octet.
   }
 
-  if (n == 0 && src.empty()) {
-    return true;
+  // If there's text left, or not all the octets were filled, fail.
+  if (! src.empty() || n != 0) {
+    _addr = INADDR_ANY;
+    return false;
   }
-  _addr = INADDR_ANY;
-  return false;
+
+  return true;
 }
 
 IP4Addr::IP4Addr(sockaddr_in const *sa) : _addr(reorder(sa->sin_addr.s_addr)) {}
@@ -295,6 +311,16 @@
   return sa;
 }
 
+// --- IPv6
+
+sockaddr *
+IP6Addr::copy_to(sockaddr *sa, in_port_t port) const {
+  IPEndpoint addr(sa);
+  self_type::reorder(addr.sa6.sin6_addr, _addr._raw);
+  addr.network_order_port() = port;
+  return sa;
+}
+
 int
 IP6Addr::cmp(const self_type &that) const {
   return *this < that ? -1 : *this > that ? 1 : 0;
@@ -439,6 +465,15 @@
   self_type::reorder(dst.data() + WORD_SIZE, src.s6_addr + WORD_SIZE);
 }
 
+IPAddr::IPAddr(IPEndpoint const &addr) {
+  this->assign(&addr.sa);
+}
+
+IPAddr &
+IPAddr::operator=(IPEndpoint const &addr) {
+  return this->assign(&addr.sa);
+}
+
 bool
 IPAddr::load(const std::string_view &text) {
   TextView src{text};
@@ -576,9 +611,9 @@
 IPMask
 IPMask::mask_for(IPAddr const &addr) {
   if (addr.is_ip4()) {
-    return self_type::mask_for(static_cast<IP4Addr const &>(addr));
+    return self_type::mask_for(addr.ip4());
   } else if (addr.is_ip6()) {
-    return self_type::mask_for(static_cast<IP6Addr const &>(addr));
+    return self_type::mask_for(addr.ip6());
   }
   return {};
 }
@@ -634,6 +669,119 @@
   return {MASK, MASK};
 }
 
+// --- SRV ---
+
+IP4Srv::IP4Srv(swoc::TextView text) {
+  this->load(text);
+}
+
+bool
+IP4Srv::load(swoc::TextView text) {
+  TextView addr_text, port_text, rest;
+  if (IPEndpoint::tokenize(text, &addr_text, &port_text, &rest)) {
+    if (rest.empty()) {
+      uintmax_t n = 0;
+      if (!port_text.empty()) {
+        n = swoc::svtou(port_text, &rest);
+        if (rest.size() != port_text.size() || n > std::numeric_limits<in_port_t>::max()) {
+          return false; // bad port.
+        }
+      }
+      IP4Addr addr;
+      if (addr.load(addr_text)) {
+        this->assign(addr, n);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+IP6Srv::IP6Srv(swoc::TextView text) {
+  this->load(text);
+}
+
+bool
+IP6Srv::load(swoc::TextView text) {
+  TextView addr_text, port_text, rest;
+  if (IPEndpoint::tokenize(text, &addr_text, &port_text, &rest)) {
+    if (rest.empty()) {
+      uintmax_t n = 0;
+      if (!port_text.empty()) {
+        n = swoc::svtou(port_text, &rest);
+        if (rest.size() != port_text.size() || n > std::numeric_limits<in_port_t>::max()) {
+          return false; // bad port.
+        }
+      }
+      IP6Addr addr;
+      if (addr.load(addr_text)) {
+        this->assign(addr, n);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+IPSrv::IPSrv(swoc::TextView text) {
+  this->load(text);
+}
+
+bool
+IPSrv::load(swoc::TextView text) {
+  TextView addr_text, port_text, rest;
+  if (IPEndpoint::tokenize(text, &addr_text, &port_text, &rest)) {
+    if (rest.empty()) {
+      uintmax_t n = 0;
+      if (!port_text.empty()) {
+        n = swoc::svtou(port_text, &rest);
+        if (rest.size() != port_text.size() || n > std::numeric_limits<in_port_t>::max()) {
+          return false; // bad port.
+        }
+      }
+      IPAddr addr;
+      if (addr.load(addr_text)) {
+        this->assign(addr, n);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+
+IPSrv::IPSrv(IPAddr addr, in_port_t port) {
+  _family = addr.family();
+  if (addr.is_ip4()) {
+    _srv._ip4.assign(addr.ip4(), port);
+  } else if (addr.is_ip6()) {
+    _srv._ip6.assign(addr.ip6(), port);
+  } else {
+    _family = AF_UNSPEC;
+  }
+}
+
+IPSrv::IPSrv(IPEndpoint const& ep) {
+  if (ep.is_ip4()) {
+    _family = _srv._ip4.family();
+    _srv._ip4.assign(&ep.sa4);
+  } else if (ep.is_ip6()) {
+    _family = _srv._ip6.family();
+    _srv._ip6.assign(&ep.sa6);
+  }
+}
+
+auto IPSrv::assign(const sockaddr *sa) -> self_type & {
+  if (AF_INET == sa->sa_family) {
+    _family = AF_INET;
+    _srv._ip4.assign(reinterpret_cast<sockaddr_in const *>(sa));
+  } else if (AF_INET6 == sa->sa_family) {
+    _family = AF_INET6;
+    _srv._ip6.assign(reinterpret_cast<sockaddr_in6 const *>(sa));
+  }
+  return *this;
+}
+
 // ++ IPNet ++
 
 bool
@@ -879,20 +1027,16 @@
 
 bool
 IPRange::load(std::string_view const &text) {
-  static const string_view CHARS{".:"};
-  if ( auto idx = text.find_first_of(CHARS) ; 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;
-      }
+  if ( auto idx = text.find_first_of(':') ; idx != text.npos ) {
+    if (_range._ip6.load(text)) {
+      _family = AF_INET6;
+      return true;
     }
+  } else if (_range._ip4.load(text)) {
+    _family = AF_INET;
+    return true;
   }
+
   return false;
 }
 
diff --git a/mgmt/Alarms.cc b/mgmt/Alarms.cc
deleted file mode 100644
index 35f7d64..0000000
--- a/mgmt/Alarms.cc
+++ /dev/null
@@ -1,391 +0,0 @@
-/** @file
-
-  Function defs for the Alarms keeper.
-
-  @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 "tscore/ink_platform.h"
-#include "tscore/ink_string.h"
-#include "tscore/ink_file.h"
-#include "tscore/ink_time.h"
-#include "MgmtUtils.h"
-#include "Alarms.h"
-
-#include "records/P_RecCore.h"
-
-const char *alarmText[] = {
-  "Unknown Alarm",                                        // 0
-  "[TrafficManager] Traffic Server process was reset.",   // 1
-  "[TrafficManager] Traffic Server process established.", // 2
-  "Invalid Configuration",                                // 3
-  "System Error",                                         // 4
-  "Cache Error",                                          // 5
-  "Cache Warning",                                        // 6
-  "Logging Error",                                        // 7
-  "Logging Warning",                                      // 8
-  "Alarms configuration update failed",                   // 9
-  "Librecords",                                           // 10 (unclear if needed / used)
-  "Plugin set configuration",                             // 11 (unclear if needed / used)
-};
-
-const int alarmTextNum = sizeof(alarmText) / sizeof(char *);
-
-// Return the alarm script directory. Use proxy.config.alarm.abs_path if it is
-// set, falling back to proxy.config.bin_path otherwise.
-static char *
-alarm_script_dir()
-{
-  char *path;
-
-  path = REC_readString("proxy.config.alarm.abs_path", nullptr);
-  if (path && *path) {
-    return path;
-  }
-
-  return ats_stringdup(RecConfigReadBinDir());
-}
-
-Alarms::Alarms()
-{
-  cur_cb = 0;
-  ink_mutex_init(&mutex);
-} /* End Alarms::Alarms */
-
-Alarms::~Alarms()
-{
-  for (auto &&it : local_alarms) {
-    ats_free(it.second);
-  }
-  for (auto &&it : remote_alarms) {
-    ats_free(it.second);
-  }
-  ink_mutex_destroy(&mutex);
-} /* End Alarms::Alarms */
-
-void
-Alarms::registerCallback(AlarmCallbackFunc func)
-{
-  char cb_buf[80];
-
-  ink_mutex_acquire(&mutex);
-  snprintf(cb_buf, sizeof(cb_buf), "%d", cur_cb++);
-  Debug("alarm", "[Alarms::registerCallback] Registering Alarms callback");
-  cblist.emplace(cb_buf, func);
-  ink_mutex_release(&mutex);
-} /* End Alarms::registerCallback */
-
-bool
-Alarms::isCurrentAlarm(alarm_t a, char *ip)
-{
-  bool ret = false;
-  char buf[80];
-
-  ink_mutex_acquire(&mutex);
-  if (!ip) {
-    snprintf(buf, sizeof(buf), "%d", a);
-  } else {
-    snprintf(buf, sizeof(buf), "%d-%s", a, ip);
-  }
-
-  if (!ip && local_alarms.find(buf) != local_alarms.end()) {
-    ret = true;
-  } else if (ip && remote_alarms.find(buf) != remote_alarms.end()) {
-    ret = true;
-  }
-  ink_mutex_release(&mutex);
-  return ret;
-} /* End Alarms::isCurrentAlarm */
-
-void
-Alarms::resolveAlarm(alarm_t a, char *ip)
-{
-  char buf[80];
-
-  ink_mutex_acquire(&mutex);
-  if (!ip) {
-    snprintf(buf, sizeof(buf), "%d", a);
-  } else {
-    snprintf(buf, sizeof(buf), "%d-%s", a, ip);
-  }
-
-  if (!ip && local_alarms.find(buf) != local_alarms.end()) {
-    Alarm *hash_value = local_alarms[buf];
-    local_alarms.erase(buf);
-    ats_free(hash_value->description);
-    ats_free(hash_value);
-  } else if (ip && remote_alarms.find(buf) != remote_alarms.end()) {
-    Alarm *hash_value = remote_alarms[buf];
-    remote_alarms.erase(buf);
-    ats_free(hash_value->description);
-    ats_free(hash_value);
-  }
-  ink_mutex_release(&mutex);
-
-  return;
-} /* End Alarms::resolveAlarm */
-
-void
-Alarms::signalAlarm(alarm_t a, const char *desc, const char *ip)
-{
-  static time_t last_sent           = 0;
-  static char prev_alarm_text[2048] = "";
-
-  int priority;
-  char buf[80];
-  Alarm *atmp;
-
-  /* Assign correct priorities */
-  switch (a) {
-  case MGMT_ALARM_PROXY_CACHE_ERROR:
-    priority = 1; // INKqa07595
-    break;
-  case MGMT_ALARM_PROXY_CACHE_WARNING:
-    return;
-  case MGMT_ALARM_PROXY_PROCESS_DIED:
-    priority = 1;
-    break;
-  case MGMT_ALARM_PROXY_PROCESS_BORN:
-    mgmt_log("[Alarms::signalAlarm] Server Process born\n");
-    return;
-  default:
-    priority = 2;
-    break;
-  }
-
-  /* Quick hack to buffer repeat alarms and only send every 15 min */
-  if (desc && (priority == 1 || priority == 2) && !ip) {
-    if (strcmp(prev_alarm_text, desc) == 0) { /* a repeated alarm */
-      time_t time_delta = time(nullptr) - last_sent;
-      if (time_delta < 900) {
-        mgmt_log("[Alarms::signalAlarm] Skipping Alarm: '%s'\n", desc);
-        return;
-      } else {
-        last_sent = time(nullptr);
-      }
-    } else {
-      ink_strlcpy(prev_alarm_text, desc, sizeof(prev_alarm_text));
-      last_sent = time(nullptr);
-    }
-  }
-
-  Debug("alarm", "[Alarms::signalAlarm] Sending Alarm: '%s'", desc);
-
-  if (!desc) {
-    desc = const_cast<char *>(getAlarmText(a));
-  }
-
-  /*
-   * Exec alarm bin for priority alarms every time, regardless if they are
-   * potentially duplicates. However, only exec this for you own alarms,
-   * don't want every node in the cluster reporting the same alarm.
-   */
-  if (priority == 1 && !ip) {
-    execAlarmBin(desc);
-  }
-
-  ink_mutex_acquire(&mutex);
-  if (!ip) {
-    snprintf(buf, sizeof(buf), "%d", a);
-    if (local_alarms.find(buf) != local_alarms.end()) {
-      ink_mutex_release(&mutex);
-      return;
-    }
-  } else {
-    snprintf(buf, sizeof(buf), "%d-%s", a, ip);
-    if (auto it = remote_alarms.find(buf); it != remote_alarms.end()) {
-      // Reset the seen flag so that we know the remote alarm is
-      //   still active
-      atmp       = it->second;
-      atmp->seen = true;
-      ink_mutex_release(&mutex);
-      return;
-    }
-  }
-
-  atmp              = static_cast<Alarm *>(ats_malloc(sizeof(Alarm)));
-  atmp->type        = a;
-  atmp->linger      = true;
-  atmp->seen        = true;
-  atmp->priority    = priority;
-  atmp->description = nullptr;
-
-  if (!ip) {
-    atmp->local        = true;
-    atmp->inet_address = 0;
-    local_alarms.emplace(buf, atmp);
-  } else {
-    atmp->local        = false;
-    atmp->inet_address = inet_addr(ip);
-    local_alarms.emplace(buf, atmp);
-  }
-
-  // Swap desc with time-stamped description.  Kinda hackish
-  // Temporary until we get a new
-  // alarm system in place.  TS 5.0.0, 02/08/2001
-  time_t my_time_t;
-  char my_ctime_str[32];
-  time(&my_time_t);
-  ink_ctime_r(&my_time_t, my_ctime_str);
-  char *p = my_ctime_str;
-  while (*p != '\n' && *p != '\0') {
-    p++;
-  }
-  if (*p == '\n') {
-    *p = '\0';
-  }
-  const size_t sz = sizeof(char) * (strlen(desc) + strlen(my_ctime_str) + 4);
-  ats_free(atmp->description);
-  atmp->description = static_cast<char *>(ats_malloc(sz));
-  snprintf(atmp->description, sz, "[%s] %s", my_ctime_str, desc);
-
-  ink_mutex_release(&mutex);
-
-  for (auto &&it : cblist) {
-    AlarmCallbackFunc func = it.second;
-    Debug("alarm", "[Alarms::signalAlarm] invoke callback for %d", a);
-    (*(func))(a, ip, desc);
-  }
-
-  /* Priority 2 alarms get signaled if they are the first unsolved occurrence. */
-  if (priority == 2 && !ip) {
-    execAlarmBin(desc);
-  }
-
-} /* End Alarms::signalAlarm */
-
-/*
- * resetSeenFlag(...)
- *   Function resets the "seen" flag for a given peer's alarms. This allows
- * us to flush alarms that may have expired naturally or were dealt.
- */
-void
-Alarms::resetSeenFlag(char *ip)
-{
-  ink_mutex_acquire(&mutex);
-  for (auto &&it : remote_alarms) {
-    std::string const &key = it.first;
-    Alarm *tmp             = it.second;
-    if (key.find(ip) != std::string::npos) {
-      tmp->seen = false;
-    }
-  }
-  ink_mutex_release(&mutex);
-  return;
-} /* End Alarms::resetSeenFlag */
-
-/*
- * clearUnSeen(...)
- *   This function is a sweeper function to clean up those alarms that have
- * been taken care of through other local managers or at the peer itself.
- */
-void
-Alarms::clearUnSeen(char *ip)
-{
-  ink_mutex_acquire(&mutex);
-  for (auto &&it : remote_alarms) {
-    std::string const &key = it.first;
-    Alarm *tmp             = it.second;
-
-    if (key.find(ip) != std::string::npos) { /* Make sure alarm is for correct ip */
-      if (!tmp->seen) {                      /* Make sure we did not see it in peer's report */
-        remote_alarms.erase(key);
-        ats_free(tmp->description);
-        ats_free(tmp);
-      }
-    }
-  }
-  ink_mutex_release(&mutex);
-  return;
-} /* End Alarms::clearUnSeen */
-
-void
-Alarms::execAlarmBin(const char *desc)
-{
-  ats_scoped_str bindir(alarm_script_dir());
-  char cmd_line[MAXPATHLEN];
-
-  ats_scoped_str alarm_bin(REC_readString("proxy.config.alarm.bin", nullptr));
-  ats_scoped_str alarm_email_from_name;
-  ats_scoped_str alarm_email_from_addr;
-  ats_scoped_str alarm_email_to_addr;
-
-  pid_t pid;
-
-  // If there's no alarm script configured, don't even bother.
-  if (!alarm_bin || *alarm_bin == '\0') {
-    return;
-  }
-
-  ink_filepath_make(cmd_line, sizeof(cmd_line), bindir, alarm_bin);
-
-#ifdef POSIX_THREAD
-  if ((pid = fork()) < 0)
-#else
-  if ((pid = fork1()) < 0)
-#endif
-  {
-    mgmt_elog(errno, "[Alarms::execAlarmBin] Unable to fork1 process\n");
-  } else if (pid > 0) { /* Parent */
-    int status;
-    bool script_done = false;
-    time_t timeout   = static_cast<time_t>(REC_readInteger("proxy.config.alarm.script_runtime", nullptr));
-    if (!timeout) {
-      timeout = 5; // default time = 5 secs
-    }
-    time_t time_delta = 0;
-    time_t first_time = time(nullptr);
-    while (time_delta <= timeout) {
-      // waitpid will return child's pid if status is available
-      // or -1 if there is some problem; returns 0 if child status
-      // is not available
-      if (waitpid(pid, &status, WNOHANG) != 0) {
-        Debug("alarm", "[Alarms::execAlarmBin] child pid %" PRId64 " has status", (int64_t)pid);
-        script_done = true;
-        break;
-      }
-      time_delta = time(nullptr) - first_time;
-    }
-    // need to kill the child script process if it's not complete
-    if (!script_done) {
-      Debug("alarm", "[Alarms::execAlarmBin] kill child pid %" PRId64 "", (int64_t)pid);
-      kill(pid, SIGKILL);
-      waitpid(pid, &status, 0); // to reap the thread
-    }
-  } else {
-    int res = execl(cmd_line, (const char *)alarm_bin, desc, (char *)nullptr);
-
-    _exit(res);
-  }
-}
-
-//
-// getAlarmText
-//
-// returns the corresponding text for the alarm id
-//
-const char *
-Alarms::getAlarmText(alarm_t id)
-{
-  if (id < alarmTextNum) {
-    return alarmText[id];
-  } else {
-    return alarmText[0]; // "Unknown Alarm";
-  }
-}
diff --git a/mgmt/Alarms.h b/mgmt/Alarms.h
deleted file mode 100644
index f2e6259..0000000
--- a/mgmt/Alarms.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/** @file
-
-  Class definitions for alarms keeper, class keeps a queue of Alarm
-  objects. Can be polled on status of alarms.
-
-  @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.
- */
-
-#pragma once
-
-#include "tscore/ink_mutex.h"
-#include <unordered_map>
-#include <string>
-
-class AppVersionInfo;
-
-/***********************************************************************
- *
- * MODULARIZATION: if you are adding new alarms, please be sure to add
- *                 the corresponding alarms in src/records/I_RecAlarms.h
- *
- ***********************************************************************/
-
-// When adding new alarms, please make sure add the
-//   corresponding alarm text
-//
-#define MGMT_ALARM_UNDEFINED 0
-
-#define MGMT_ALARM_PROXY_PROCESS_DIED 1
-#define MGMT_ALARM_PROXY_PROCESS_BORN 2
-#define MGMT_ALARM_PROXY_CONFIG_ERROR 3
-#define MGMT_ALARM_PROXY_SYSTEM_ERROR 4
-#define MGMT_ALARM_PROXY_CACHE_ERROR 5
-#define MGMT_ALARM_PROXY_CACHE_WARNING 6
-#define MGMT_ALARM_PROXY_LOGGING_ERROR 7
-#define MGMT_ALARM_PROXY_LOGGING_WARNING 8
-#define MGMT_ALARM_CONFIG_UPDATE_FAILED 9
-
-extern const char *alarmText[];
-extern const int alarmTextNum;
-
-typedef int alarm_t;
-typedef void (*AlarmCallbackFunc)(alarm_t, const char *, const char *);
-
-typedef struct _alarm {
-  alarm_t type;
-  int priority;
-  bool linger;
-  bool local;
-  bool seen;
-  unsigned long inet_address; /* If not local */
-  char *description;
-} Alarm;
-
-class Alarms
-{
-public:
-  Alarms();
-  ~Alarms();
-
-  void registerCallback(AlarmCallbackFunc func);
-  bool isCurrentAlarm(alarm_t a, char *ip = nullptr);
-
-  void signalAlarm(alarm_t t, const char *desc, const char *ip = nullptr);
-  void resolveAlarm(alarm_t a, char *ip = nullptr);
-
-  void constructAlarmMessage(const AppVersionInfo &version, char *ip, char *message, int max);
-  void resetSeenFlag(char *ip);
-  void clearUnSeen(char *ip);
-
-  void execAlarmBin(const char *desc);
-
-  const char *getAlarmText(alarm_t id);
-  std::unordered_map<std::string, Alarm *> const &
-  getLocalAlarms()
-  {
-    return local_alarms;
-  }
-
-private:
-  int cur_cb;
-  ink_mutex mutex;
-
-  std::unordered_map<std::string, AlarmCallbackFunc> cblist;
-  std::unordered_map<std::string, Alarm *> local_alarms;
-  std::unordered_map<std::string, Alarm *> remote_alarms;
-}; /* End class Alarms */
diff --git a/mgmt/BaseManager.cc b/mgmt/BaseManager.cc
deleted file mode 100644
index 0f3d8a1..0000000
--- a/mgmt/BaseManager.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-/** @file
-
-  Member function definitions for Base Manager class.
-
-  @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 "tscore/ink_memory.h"
-#include "tscore/ink_mutex.h"
-#include "BaseManager.h"
-
-BaseManager::BaseManager()
-{
-  ink_sem_init(&q_sem, 0);
-}
-
-BaseManager::~BaseManager()
-{
-  while (!queue.empty()) {
-    ats_free(queue.front());
-    queue.pop();
-  }
-}
-
-void
-BaseManager::enqueue(MgmtMessageHdr *mh)
-{
-  std::lock_guard lock(q_mutex);
-  queue.emplace(mh);
-  ink_sem_post(&q_sem);
-}
-
-bool
-BaseManager::queue_empty()
-{
-  std::lock_guard lock(q_mutex);
-  return queue.empty();
-}
-
-MgmtMessageHdr *
-BaseManager::dequeue()
-{
-  MgmtMessageHdr *msg{nullptr};
-
-  ink_sem_wait(&q_sem);
-  {
-    std::lock_guard lock(q_mutex);
-    msg = queue.front();
-    queue.pop();
-  }
-  return msg;
-}
-
-int
-BaseManager::registerMgmtCallback(int msg_id, MgmtCallback const &cb)
-{
-  auto &cb_list{mgmt_callback_table[msg_id]};
-  cb_list.emplace_back(cb);
-  return msg_id;
-}
-
-void
-BaseManager::executeMgmtCallback(int msg_id, ts::MemSpan<void> span)
-{
-  if (auto it = mgmt_callback_table.find(msg_id); it != mgmt_callback_table.end()) {
-    for (auto &&cb : it->second) {
-      cb(span);
-    }
-  }
-}
diff --git a/mgmt/BaseManager.h b/mgmt/BaseManager.h
deleted file mode 100644
index 322b654..0000000
--- a/mgmt/BaseManager.h
+++ /dev/null
@@ -1,145 +0,0 @@
-/** @file
-
-  Base Manager Class, base class for all managers.
-
-  @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.
- */
-
-#pragma once
-
-#include <list>
-#include <queue>
-#include <mutex>
-#include <unordered_map>
-
-#include "tscore/ink_thread.h"
-#include "tscore/ink_mutex.h"
-#include "tscpp/util/MemSpan.h"
-
-#include "MgmtDefs.h"
-#include "MgmtMarshall.h"
-
-/*
- * MgmtEvent defines.
- */
-
-// Event flows: traffic manager -> traffic server
-#define MGMT_EVENT_SYNC_KEY 10000
-#define MGMT_EVENT_SHUTDOWN 10001
-#define MGMT_EVENT_RESTART 10002
-#define MGMT_EVENT_BOUNCE 10003
-#define MGMT_EVENT_CLEAR_STATS 10004
-#define MGMT_EVENT_CONFIG_FILE_UPDATE 10005
-#define MGMT_EVENT_PLUGIN_CONFIG_UPDATE 10006
-#define MGMT_EVENT_ROLL_LOG_FILES 10008
-#define MGMT_EVENT_LIBRECORDS 10009
-// 10010 is unused
-// cache storage operations - each is a distinct event.
-// this is done because the code paths share nothing but boilerplate logic
-// so it's easier to do this than to try to encode an opcode and yet another
-// case statement.
-#define MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE 10011
-#define MGMT_EVENT_LIFECYCLE_MESSAGE 10012
-#define MGMT_EVENT_DRAIN 10013
-#define MGMT_EVENT_HOST_STATUS_UP 10014
-#define MGMT_EVENT_HOST_STATUS_DOWN 10015
-
-/***********************************************************************
- *
- * MODULARIZATION: if you are adding new signals, please ensure to add
- *                 the corresponding signals in librecords/I_RecSignals.h
- *
- *
- ***********************************************************************/
-
-// Signal flows: traffic server -> traffic manager
-#define MGMT_SIGNAL_PID 0
-
-#define MGMT_SIGNAL_PROXY_PROCESS_DIED 1
-#define MGMT_SIGNAL_PROXY_PROCESS_BORN 2
-#define MGMT_SIGNAL_CONFIG_ERROR 3
-#define MGMT_SIGNAL_SYSTEM_ERROR 4
-#define MGMT_SIGNAL_CACHE_ERROR 5
-#define MGMT_SIGNAL_CACHE_WARNING 6
-#define MGMT_SIGNAL_LOGGING_ERROR 7
-#define MGMT_SIGNAL_LOGGING_WARNING 8
-#define MGMT_SIGNAL_PLUGIN_SET_CONFIG 9
-
-// This are additional on top of the ones defined in Alarms.h. Que?
-#define MGMT_SIGNAL_LIBRECORDS 10
-#define MGMT_SIGNAL_CONFIG_FILE_CHILD 11
-
-struct MgmtMessageHdr {
-  int msg_id;
-  int data_len;
-  ts::MemSpan<void>
-  payload()
-  {
-    return {this + 1, static_cast<size_t>(data_len)};
-  }
-};
-
-class BaseManager
-{
-  using MgmtCallbackList = std::list<MgmtCallback>;
-
-public:
-  BaseManager();
-
-  ~BaseManager();
-
-  /** Associate a callback function @a func with message identifier @a msg_id.
-   *
-   * @param msg_id Message identifier for the callback.
-   * @param func The callback function.
-   * @return @a msg_id on success, -1 on failure.
-   *
-   * @a msg_id should be one of the @c MGMT_EVENT_... values.
-   *
-   * If a management message with @a msg is received, the callbacks for that message id
-   * are invoked and passed the message payload (not including the header).
-   */
-  int registerMgmtCallback(int msg_id, MgmtCallback const &func);
-
-  /// Add a @a msg to the queue.
-  /// This must be the entire message as read off the wire including the header.
-  void enqueue(MgmtMessageHdr *msg);
-
-  /// Current size of the queue.
-  /// @note This does not block on the semaphore.
-  bool queue_empty();
-
-  /// Dequeue a msg.
-  /// This waits on the semaphore for a message to arrive.
-  MgmtMessageHdr *dequeue();
-
-protected:
-  void executeMgmtCallback(int msg_id, ts::MemSpan<void> span);
-
-  /// The mapping from an event type to a list of callbacks to invoke.
-  std::unordered_map<int, MgmtCallbackList> mgmt_callback_table;
-
-  /// Message queue.
-  // These holds the entire message object, including the header.
-  std::queue<MgmtMessageHdr *> queue;
-  /// Locked access to the queue.
-  std::mutex q_mutex;
-  /// Semaphore to signal queue state.
-  ink_semaphore q_sem;
-};
diff --git a/mgmt/ConfigManager.cc b/mgmt/ConfigManager.cc
deleted file mode 100644
index 2f2a830..0000000
--- a/mgmt/ConfigManager.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-/** @file
-
-  This file contains code for class to allow management of configuration files
-
-  @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 "tscore/ink_platform.h"
-#include "tscore/ink_memory.h"
-#include "tscore/ink_time.h"
-#include "Alarms.h"
-#include "LocalManager.h"
-#include "ConfigManager.h"
-#include "WebMgmtUtils.h"
-#include "ExpandingArray.h"
-#include "MgmtSocket.h"
-#include "tscore/I_Layout.h"
-#include "FileManager.h"
-#include "ProxyConfig.h"
-
-#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
-#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtimespec.tv_nsec)
-#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
-#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtim.tv_nsec)
-#else
-#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000)
-#endif
-
-ConfigManager::ConfigManager(const char *fileName_, const char *configName_, bool root_access_needed_, bool isRequired_,
-                             ConfigManager *parentConfig_)
-  : root_access_needed(root_access_needed_), isRequired(isRequired_), parentConfig(parentConfig_)
-{
-  ExpandingArray existVer(25, true); // Existing versions
-  struct stat fileInfo;
-  ink_assert(fileName_ != nullptr);
-
-  // parent must not also have a parent
-  if (parentConfig) {
-    ink_assert(parentConfig->parentConfig == nullptr);
-  }
-
-  // Copy the file name.
-  fileName   = std::string{fileName_};
-  configName = std::string{configName_};
-
-  ink_mutex_init(&fileAccessLock);
-  // Check to make sure that our configuration file exists
-  //
-  if (statFile(&fileInfo) < 0) {
-    mgmt_log("[ConfigManager::ConfigManager] %s Unable to load: %s", fileName.c_str(), strerror(errno));
-
-    if (isRequired) {
-      mgmt_fatal(0, "[ConfigManager::ConfigManager] Unable to open required configuration file %s.\n\tStat failed : %s\n",
-                 fileName.c_str(), strerror(errno));
-    }
-  } else {
-    fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
-  }
-}
-
-//
-//
-// int ConfigManager::statFile()
-//
-//  A wrapper for stat()
-//
-int
-ConfigManager::statFile(struct stat *buf)
-{
-  int statResult;
-  std::string sysconfdir(RecConfigReadConfigDir());
-  std::string filePath = Layout::get()->relative_to(sysconfdir, fileName);
-
-  statResult = root_access_needed ? elevating_stat(filePath.c_str(), buf) : stat(filePath.c_str(), buf);
-
-  return statResult;
-}
-
-// bool ConfigManager::checkForUserUpdate(RollBackCheckType how)
-//
-//  Called to check if the file has been changed  by the user.
-//  Timestamps are compared to see if a change occurred
-bool
-ConfigManager::checkForUserUpdate(RollBackCheckType how)
-{
-  struct stat fileInfo;
-  bool result;
-
-  ink_mutex_acquire(&fileAccessLock);
-
-  if (this->statFile(&fileInfo) < 0) {
-    ink_mutex_release(&fileAccessLock);
-    return false;
-  }
-
-  if (fileLastModified < TS_ARCHIVE_STAT_MTIME(fileInfo)) {
-    if (how == ROLLBACK_CHECK_AND_UPDATE) {
-      fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
-      if (!this->isChildManaged()) {
-        configFiles->fileChanged(fileName.c_str(), configName.c_str());
-      }
-      mgmt_log("User has changed config file %s\n", fileName.c_str());
-    }
-    result = true;
-  } else {
-    result = false;
-  }
-
-  ink_mutex_release(&fileAccessLock);
-  return result;
-}
diff --git a/mgmt/ConfigManager.h b/mgmt/ConfigManager.h
deleted file mode 100644
index f921061..0000000
--- a/mgmt/ConfigManager.h
+++ /dev/null
@@ -1,132 +0,0 @@
-/** @file
-
-  Interface for class to allow management of configuration files
-
-  @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.
- */
-
-#pragma once
-
-#include "tscore/ink_mutex.h"
-#include "tscore/List.h"
-
-#include <string>
-
-class FileManager;
-class TextBuffer;
-
-class ExpandingArray;
-
-enum RollBackCheckType {
-  ROLLBACK_CHECK_AND_UPDATE,
-  ROLLBACK_CHECK_ONLY,
-};
-
-//
-//  class ConfigManager
-//
-//  public functions
-//
-//  checkForUserUpdate() - compares the last known modification time
-//    of the active version of the file with that files current modification
-//    time.  Returns true if the file has been changed manually or false
-//    if it hasn't
-//
-// private functions
-//
-//  statFile(struct stat*) - a wrapper for stat(), using layout engine
-//
-class ConfigManager
-{
-public:
-  // fileName_ should be rooted or a base file name.
-  ConfigManager(const char *fileName_, const char *configName_, bool root_access_needed, bool isRequired_,
-                ConfigManager *parentConfig_);
-  ~ConfigManager() = default;
-
-  // Manual take out of lock required
-  void
-  acquireLock()
-  {
-    ink_mutex_acquire(&fileAccessLock);
-  };
-
-  void
-  releaseLock()
-  {
-    ink_mutex_release(&fileAccessLock);
-  };
-
-  // Check if a file has changed, automatically holds the lock. Used by FileManager.
-  bool checkForUserUpdate(RollBackCheckType);
-
-  // These are getters, for FileManager to get info about a particular configuration.
-  const char *
-  getFileName() const
-  {
-    return fileName.c_str();
-  }
-
-  const char *
-  getConfigName() const
-  {
-    return configName.c_str();
-  }
-
-  bool
-  isChildManaged() const
-  {
-    return parentConfig != nullptr;
-  }
-
-  ConfigManager *
-  getParentConfig() const
-  {
-    return parentConfig;
-  }
-
-  bool
-  rootAccessNeeded() const
-  {
-    return root_access_needed;
-  }
-
-  bool
-  getIsRequired() const
-  {
-    return isRequired;
-  }
-
-  FileManager *configFiles = nullptr; // Manager to notify on an update.
-
-  // noncopyable
-  ConfigManager(const ConfigManager &) = delete;
-  ConfigManager &operator=(const ConfigManager &) = delete;
-
-private:
-  int statFile(struct stat *buf);
-
-  ink_mutex fileAccessLock;
-  std::string fileName;
-  std::string configName;
-  bool root_access_needed;
-  bool isRequired;
-  ConfigManager *parentConfig;
-  time_t fileLastModified = 0;
-};
diff --git a/mgmt/DerivativeMetrics.cc b/mgmt/DerivativeMetrics.cc
index 16572db..3a39875 100644
--- a/mgmt/DerivativeMetrics.cc
+++ b/mgmt/DerivativeMetrics.cc
@@ -30,7 +30,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // This currently only supports one type of derivative metrics: Sums() of other, existing metrics. It's ok to add
-// additional metrics here, and we prefer to call them proxy.process (since, hopefully in the future, traffic_manager dies).
+// additional metrics here, and we prefer to call them proxy.process now that traffic_manager died.
 //
 static const std::vector<DerivativeSum> sum_metrics = {
   // Total bytes of client request body + headers
diff --git a/mgmt/DerivativeMetrics.h b/mgmt/DerivativeMetrics.h
index 2109f02..cc8db1b 100644
--- a/mgmt/DerivativeMetrics.h
+++ b/mgmt/DerivativeMetrics.h
@@ -24,8 +24,7 @@
 #pragma once
 
 #include <tuple>
-
-#include "records/I_RecLocal.h"
+#include "records/I_RecDefs.h"
 
 using DerivativeSum = std::tuple<const char *, RecDataT, std::vector<const char *>>;
 
diff --git a/mgmt/FileManager.cc b/mgmt/FileManager.cc
deleted file mode 100644
index fce09a0..0000000
--- a/mgmt/FileManager.cc
+++ /dev/null
@@ -1,265 +0,0 @@
-/** @file
-
-  Code for class to manage configuration updates
-
-  @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 "FileManager.h"
-#include "tscore/ink_platform.h"
-#include "tscore/ink_file.h"
-#include "ConfigManager.h"
-#include "WebMgmtUtils.h"
-
-#include <vector>
-#include <algorithm>
-
-FileManager::FileManager()
-{
-  ink_mutex_init(&accessLock);
-  ink_mutex_init(&cbListLock);
-}
-
-// FileManager::~FileManager
-//
-//  There is only FileManager object in the process and it
-//     should never need to be destructed except at
-//     program exit
-//
-FileManager::~FileManager()
-{
-  callbackListable *cb;
-
-  // Let other operations finish and do not start any new ones
-  ink_mutex_acquire(&accessLock);
-
-  for (cb = cblist.pop(); cb != nullptr; cb = cblist.pop()) {
-    delete cb;
-  }
-  for (auto &&it : bindings) {
-    delete it.second;
-  }
-
-  ink_mutex_release(&accessLock);
-  ink_mutex_destroy(&accessLock);
-  ink_mutex_destroy(&cbListLock);
-}
-
-// void FileManager::registerCallback(FileCallbackFunc func)
-//
-//  Adds a new callback function
-//    callbacks are made whenever a configuration file has
-//    changed
-//
-//  The callback function is responsible for free'ing
-//    the string the string it is passed
-//
-void
-FileManager::registerCallback(FileCallbackFunc func)
-{
-  callbackListable *newcb = new callbackListable();
-  ink_assert(newcb != nullptr);
-  newcb->func = func;
-  ink_mutex_acquire(&cbListLock);
-  cblist.push(newcb);
-  ink_mutex_release(&cbListLock);
-}
-
-// void FileManager::addFile(char* fileName, const configFileInfo* file_info,
-//  ConfigManager* parentConfig)
-//
-//  for the baseFile, creates a ConfigManager object for it
-//
-//  if file_info is not null, a WebFileEdit object is also created for
-//    the file
-//
-//  Pointers to the new objects are stored in the bindings hashtable
-//
-void
-FileManager::addFile(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
-                     ConfigManager *parentConfig)
-{
-  ink_mutex_acquire(&accessLock);
-  addFileHelper(fileName, configName, root_access_needed, isRequired, parentConfig);
-  ink_mutex_release(&accessLock);
-}
-
-// caller must hold the lock
-void
-FileManager::addFileHelper(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
-                           ConfigManager *parentConfig)
-{
-  ink_assert(fileName != nullptr);
-
-  ConfigManager *rb = new ConfigManager(fileName, configName, root_access_needed, isRequired, parentConfig);
-  rb->configFiles   = this;
-
-  bindings.emplace(rb->getFileName(), rb);
-}
-
-// bool FileManager::getConfigManagerObj(char* fileName, ConfigManager** rbPtr)
-//
-//  Sets rbPtr to the ConfigManager object associated
-//    with the passed in fileName.
-//
-//  If there is no binding, false is returned
-//
-bool
-FileManager::getConfigObj(const char *fileName, ConfigManager **rbPtr)
-{
-  ink_mutex_acquire(&accessLock);
-  auto it    = bindings.find(fileName);
-  bool found = it != bindings.end();
-  ink_mutex_release(&accessLock);
-
-  *rbPtr = found ? it->second : nullptr;
-  return found;
-}
-
-// bool FileManager::fileChanged(const char* fileName)
-//
-//  Called by the ConfigManager class whenever a config has changed
-//     Initiates callbacks
-//
-//
-void
-FileManager::fileChanged(const char *fileName, const char *configName)
-{
-  callbackListable *cb;
-  char *filenameCopy, *confignameCopy;
-  Debug("lm", "filename changed %s", fileName);
-  ink_mutex_acquire(&cbListLock);
-
-  for (cb = cblist.head; cb != nullptr; cb = cb->link.next) {
-    // Dup the string for each callback to be
-    //  defensive in case it's modified when it's not supposed to be
-    confignameCopy = ats_strdup(configName);
-    filenameCopy   = ats_strdup(fileName);
-    (*cb->func)(filenameCopy, confignameCopy);
-    ats_free(filenameCopy);
-    ats_free(confignameCopy);
-  }
-  ink_mutex_release(&cbListLock);
-}
-
-// void FileManger::rereadConfig()
-//
-//   Iterates through the list of managed files and
-//     calls ConfigManager::checkForUserUpdate on them
-//
-//   although it is tempting, DO NOT CALL FROM SIGNAL HANDLERS
-//      This function is not Async-Signal Safe.  It
-//      is thread safe
-void
-FileManager::rereadConfig()
-{
-  ConfigManager *rb;
-
-  std::vector<ConfigManager *> changedFiles;
-  std::vector<ConfigManager *> parentFileNeedChange;
-  size_t n;
-  ink_mutex_acquire(&accessLock);
-  for (auto &&it : bindings) {
-    rb = it.second;
-    // ToDo: rb->isVersions() was always true before, because numberBackups was always >= 1. So ROLLBACK_CHECK_ONLY could not
-    // happen at all...
-    if (rb->checkForUserUpdate(ROLLBACK_CHECK_AND_UPDATE)) {
-      changedFiles.push_back(rb);
-      if (rb->isChildManaged()) {
-        if (std::find(parentFileNeedChange.begin(), parentFileNeedChange.end(), rb->getParentConfig()) ==
-            parentFileNeedChange.end()) {
-          parentFileNeedChange.push_back(rb->getParentConfig());
-        }
-      }
-    }
-  }
-
-  std::vector<ConfigManager *> childFileNeedDelete;
-  n = changedFiles.size();
-  for (size_t i = 0; i < n; i++) {
-    if (changedFiles[i]->isChildManaged()) {
-      continue;
-    }
-    // for each parent file, if it is changed, then delete all its children
-    for (auto &&it : bindings) {
-      rb = it.second;
-      if (rb->getParentConfig() == changedFiles[i]) {
-        if (std::find(childFileNeedDelete.begin(), childFileNeedDelete.end(), rb) == childFileNeedDelete.end()) {
-          childFileNeedDelete.push_back(rb);
-        }
-      }
-    }
-  }
-  n = childFileNeedDelete.size();
-  for (size_t i = 0; i < n; i++) {
-    bindings.erase(childFileNeedDelete[i]->getFileName());
-    delete childFileNeedDelete[i];
-  }
-  ink_mutex_release(&accessLock);
-
-  n = parentFileNeedChange.size();
-  for (size_t i = 0; i < n; i++) {
-    if (std::find(changedFiles.begin(), changedFiles.end(), parentFileNeedChange[i]) == changedFiles.end()) {
-      fileChanged(parentFileNeedChange[i]->getFileName(), parentFileNeedChange[i]->getConfigName());
-    }
-  }
-  // INKqa11910
-  // need to first check that enable_customizations is enabled
-  bool found;
-  int enabled = static_cast<int>(REC_readInteger("proxy.config.body_factory.enable_customizations", &found));
-
-  if (found && enabled) {
-    fileChanged("proxy.config.body_factory.template_sets_dir", "proxy.config.body_factory.template_sets_dir");
-  }
-  fileChanged("proxy.config.ssl.server.ticket_key.filename", "proxy.config.ssl.server.ticket_key.filename");
-}
-
-bool
-FileManager::isConfigStale()
-{
-  ConfigManager *rb;
-  bool stale = false;
-
-  ink_mutex_acquire(&accessLock);
-  for (auto &&it : bindings) {
-    rb = it.second;
-    if (rb->checkForUserUpdate(ROLLBACK_CHECK_ONLY)) {
-      stale = true;
-      break;
-    }
-  }
-
-  ink_mutex_release(&accessLock);
-  return stale;
-}
-
-// void configFileChild(const char *parent, const char *child)
-//
-// Add child to the bindings with parentConfig
-void
-FileManager::configFileChild(const char *parent, const char *child)
-{
-  ConfigManager *parentConfig = nullptr;
-  ink_mutex_acquire(&accessLock);
-  if (auto it = bindings.find(parent); it != bindings.end()) {
-    parentConfig = it->second;
-    addFileHelper(child, "", parentConfig->rootAccessNeeded(), parentConfig->getIsRequired(), parentConfig);
-  }
-  ink_mutex_release(&accessLock);
-}
diff --git a/mgmt/FileManager.h b/mgmt/FileManager.h
deleted file mode 100644
index 64725b6..0000000
--- a/mgmt/FileManager.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/** @file
-
-  Interface for class to manage configuration updates
-
-  @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.
- */
-
-#pragma once
-
-#include "tscore/ink_mutex.h"
-#include "tscore/List.h"
-
-#include <unordered_map>
-
-class ExpandingArray;
-class ConfigManager;
-
-typedef void (*FileCallbackFunc)(char *, char *);
-
-struct callbackListable {
-public:
-  FileCallbackFunc func;
-  LINK(callbackListable, link);
-};
-
-enum lockAction_t {
-  ACQUIRE_LOCK,
-  RELEASE_LOCK,
-};
-
-//  class FileManager
-//
-//  public functions:
-//
-//  addFile(char*, char *, bool, configFileInfo*) - adds a new config file to be
-//       managed.  A ConfigManager object is created for the file.
-//       if the file_info ptr is not NULL, a WebFileEdit object
-//       is also created
-//
-//  getRollbckObj(char* , ConfigManagerPtr**) - sets *rbPtr to ConfigManager
-//       object bound to fileName.  Returns true if there is
-//       a binding and false otherwise
-//
-//  getWFEObj(char*, WebFileEdit**)  - sets *wfePtr to WebFileEdit
-//       object bound to fileName.  Returns true if there is
-//       a binding and false otherwise
-//
-//  registerCallback(FileCallbackFunc) - registers a callback function
-//       which will get called every time a managed file changes.  The
-//       callback function should NOT use the calling thread to
-//       access any ConfigManager objects or block for a long time
-//
-//  fileChanged(const char* fileName, const char *configName) - called by ConfigManager objects
-//       when their contents change.  Triggers callbacks to FileCallbackFuncs
-//
-//  isConfigStale() - returns whether the in-memory files might be stale
-//       compared to what is on disk.
-//
-//  rereadConfig() - Checks all managed files to see if they have been
-//       updated
-//  addConfigFileGroup(char* data_str, int data_size) - update config file group infos
-class FileManager
-{
-public:
-  FileManager();
-  ~FileManager();
-  void addFile(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
-               ConfigManager *parentConfig = nullptr);
-  bool getConfigObj(const char *fileName, ConfigManager **rbPtr);
-  void registerCallback(FileCallbackFunc func);
-  void fileChanged(const char *fileName, const char *configName);
-  void rereadConfig();
-  bool isConfigStale();
-  void configFileChild(const char *parent, const char *child);
-
-private:
-  ink_mutex accessLock; // Protects bindings hashtable
-  ink_mutex cbListLock; // Protects the CallBack List
-  DLL<callbackListable> cblist;
-  std::unordered_map<std::string_view, ConfigManager *> bindings;
-  void addFileHelper(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
-                     ConfigManager *parentConfig);
-};
-
-void initializeRegistry(); // implemented in AddConfigFilesHere.cc
diff --git a/mgmt/LocalManager.cc b/mgmt/LocalManager.cc
deleted file mode 100644
index 9a1a0d2..0000000
--- a/mgmt/LocalManager.cc
+++ /dev/null
@@ -1,1124 +0,0 @@
-/** @file
-
-  The Local Manager process of the management system.
-
-  @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 "tscore/ink_platform.h"
-#include "tscore/ink_sock.h"
-#include "tscore/ink_file.h"
-#include "tscore/ink_error.h"
-#include "Alarms.h"
-#include "MgmtUtils.h"
-#include "tscore/I_Layout.h"
-#include "tscore/runroot.h"
-#include "LocalManager.h"
-#include "MgmtSocket.h"
-#include "tscore/ink_cap.h"
-#include "FileManager.h"
-#include <string_view>
-#include <algorithm>
-#include "tscpp/util/TextView.h"
-#include "tscore/BufferWriter.h"
-#include "tscore/bwf_std_format.h"
-#include "tscore/Filenames.h"
-
-#if TS_USE_POSIX_CAP
-#include <sys/capability.h>
-#endif
-
-using namespace std::literals;
-static const std::string_view MGMT_OPT{"-M"};
-static const std::string_view RUNROOT_OPT{"--run-root="};
-
-void
-LocalManager::mgmtCleanup()
-{
-  close_socket(process_server_sockfd);
-  process_server_sockfd = ts::NO_FD;
-
-#if HAVE_EVENTFD
-  if (wakeup_fd != ts::NO_FD) {
-    close_socket(wakeup_fd);
-    wakeup_fd = ts::NO_FD;
-  }
-#endif
-
-  // fix me for librecords
-
-  closelog();
-  return;
-}
-
-void
-LocalManager::mgmtShutdown()
-{
-  mgmt_log("[LocalManager::mgmtShutdown] Executing shutdown request.\n");
-  processShutdown(true);
-  // WCCP TBD: Send a shutdown message to routers.
-
-  if (processRunning()) {
-    waitpid(watched_process_pid, nullptr, 0);
-#if defined(linux)
-    /* Avert race condition, wait for the thread to complete,
-       before getting one more restart process */
-    /* Workaround for bugid INKqa10060 */
-    mgmt_sleep_msec(1);
-#endif
-  }
-  mgmtCleanup();
-}
-
-void
-LocalManager::processShutdown(bool mainThread)
-{
-  mgmt_log("[LocalManager::processShutdown] Executing process shutdown request.\n");
-  if (mainThread) {
-    sendMgmtMsgToProcesses(MGMT_EVENT_SHUTDOWN, "processShutdown[main]");
-  } else {
-    signalEvent(MGMT_EVENT_SHUTDOWN, "processShutdown");
-  }
-  return;
-}
-
-void
-LocalManager::processRestart()
-{
-  mgmt_log("[LocalManager::processRestart] Executing process restart request.\n");
-  signalEvent(MGMT_EVENT_RESTART, "processRestart");
-  return;
-}
-
-void
-LocalManager::processBounce()
-{
-  mgmt_log("[LocalManager::processBounce] Executing process bounce request.\n");
-  signalEvent(MGMT_EVENT_BOUNCE, "processBounce");
-  return;
-}
-
-void
-LocalManager::processDrain(int to_drain)
-{
-  mgmt_log("[LocalManager::processDrain] Executing process drain request.\n");
-  signalEvent(MGMT_EVENT_DRAIN, to_drain ? "1" : "0");
-  return;
-}
-
-void
-LocalManager::rollLogFiles()
-{
-  mgmt_log("[LocalManager::rollLogFiles] Log files are being rolled.\n");
-  signalEvent(MGMT_EVENT_ROLL_LOG_FILES, "rollLogs");
-  return;
-}
-
-void
-LocalManager::hostStatusSetDown(const char *marshalled_req, int len)
-{
-  signalEvent(MGMT_EVENT_HOST_STATUS_DOWN, marshalled_req, len);
-  return;
-}
-
-void
-LocalManager::hostStatusSetUp(const char *marshalled_req, int len)
-{
-  signalEvent(MGMT_EVENT_HOST_STATUS_UP, marshalled_req, len);
-  return;
-}
-
-void
-LocalManager::clearStats(const char *name)
-{
-  // Clear our records and then send the signal.  There is a race condition
-  //  here where our stats could get re-updated from the proxy
-  //  before the proxy clears them, but this should be rare.
-  //
-  //  Doing things in the opposite order prevents that race
-  //   but exacerbates the race between the node and cluster
-  //   stats getting cleared by propagation of clearing the
-  //   cluster stats
-  //
-  if (name && *name) {
-    RecResetStatRecord(name);
-  } else {
-    RecResetStatRecord(RECT_NULL, true);
-  }
-
-  // If the proxy is not running, sending the signal does
-  //   not do anything.  Remove the stats file to make sure
-  //   that operation works even when the proxy is off
-  //
-  if (this->proxy_running == 0) {
-    ats_scoped_str statsPath(RecConfigReadPersistentStatsPath());
-    if (unlink(statsPath) < 0) {
-      if (errno != ENOENT) {
-        mgmt_log("[LocalManager::clearStats] Unlink of %s failed : %s\n", (const char *)statsPath, strerror(errno));
-      }
-    }
-  }
-}
-
-bool
-LocalManager::processRunning()
-{
-  if (watched_process_fd != ts::NO_FD && watched_process_pid != -1) {
-    return true;
-  } else {
-    return false;
-  }
-}
-
-LocalManager::LocalManager(bool proxy_on, bool listen) : BaseManager(), run_proxy(proxy_on), listen_for_proxy(listen)
-{
-  bool found;
-  std::string bindir(RecConfigReadBinDir());
-  std::string sysconfdir(RecConfigReadConfigDir());
-
-  manager_started_at = time(nullptr);
-
-  RecRegisterStatInt(RECT_NODE, "proxy.node.proxy_running", 0, RECP_NON_PERSISTENT);
-
-  RecInt http_enabled = REC_readInteger("proxy.config.http.enabled", &found);
-  ink_assert(found);
-  if (http_enabled && found) {
-    HttpProxyPort::loadConfig(m_proxy_ports);
-  }
-  HttpProxyPort::loadDefaultIfEmpty(m_proxy_ports);
-
-  // Get the default IP binding values.
-  RecHttpLoadIp("proxy.local.incoming_ip_to_bind", m_inbound_ip4, m_inbound_ip6);
-
-  if (access(sysconfdir.c_str(), R_OK) == -1) {
-    mgmt_log("[LocalManager::LocalManager] unable to access() directory '%s': %d, %s\n", sysconfdir.c_str(), errno,
-             strerror(errno));
-    mgmt_fatal(0, "[LocalManager::LocalManager] please set the 'TS_ROOT' environment variable\n");
-  }
-
-#if TS_HAS_WCCP
-  // Bind the WCCP address if present.
-  ats_scoped_str wccp_addr_str(REC_readString("proxy.config.wccp.addr", &found));
-  if (found && wccp_addr_str && *wccp_addr_str) {
-    wccp_cache.setAddr(inet_addr(wccp_addr_str));
-    mgmt_log("[LocalManager::LocalManager] WCCP identifying address set to %s.\n", static_cast<char *>(wccp_addr_str));
-  }
-
-  ats_scoped_str wccp_config_str(RecConfigReadConfigPath("proxy.config.wccp.services"));
-  if (wccp_config_str && strlen(wccp_config_str) > 0) {
-    bool located = true;
-    if (access(wccp_config_str, R_OK) == -1) {
-      located = false;
-    }
-
-    if (located) {
-      wccp_cache.loadServicesFromFile(wccp_config_str);
-    } else { // not located
-      mgmt_log("[LocalManager::LocalManager] WCCP service configuration file '%s' was specified but could not be found in the file "
-               "system.\n",
-               static_cast<char *>(wccp_config_str));
-    }
-  }
-#endif
-
-  process_server_timeout_secs  = REC_readInteger("proxy.config.lm.pserver_timeout_secs", &found);
-  process_server_timeout_msecs = REC_readInteger("proxy.config.lm.pserver_timeout_msecs", &found);
-  proxy_name                   = REC_readString("proxy.config.proxy_name", &found);
-  proxy_binary                 = REC_readString("proxy.config.proxy_binary", &found);
-  env_prep                     = REC_readString("proxy.config.env_prep", &found);
-
-  // Calculate proxy_binary from the absolute bin_path
-  absolute_proxy_binary = ats_stringdup(Layout::relative_to(bindir, proxy_binary));
-
-  // coverity[fs_check_call]
-  if (access(absolute_proxy_binary, R_OK | X_OK) == -1) {
-    mgmt_log("[LocalManager::LocalManager] Unable to access() '%s': %d, %s\n", absolute_proxy_binary, errno, strerror(errno));
-    mgmt_fatal(0, "[LocalManager::LocalManager] please set bin path 'proxy.config.bin_path' \n");
-  }
-
-  return;
-}
-
-LocalManager::~LocalManager()
-{
-  delete alarm_keeper;
-  ats_free(absolute_proxy_binary);
-  ats_free(proxy_name);
-  ats_free(proxy_binary);
-  ats_free(env_prep);
-}
-
-void
-LocalManager::initAlarm()
-{
-  alarm_keeper = new Alarms();
-}
-
-/*
- * initMgmtProcessServer()
- *   sets up the server socket that proxy processes connect to.
- */
-void
-LocalManager::initMgmtProcessServer()
-{
-  std::string rundir(RecConfigReadRuntimeDir());
-  std::string sockpath(Layout::relative_to(rundir, LM_CONNECTION_SERVER));
-  mode_t oldmask = umask(0);
-
-#if TS_HAS_WCCP
-  if (wccp_cache.isConfigured()) {
-    if (0 > wccp_cache.open())
-      mgmt_log("Failed to open WCCP socket\n");
-  }
-#endif
-
-  process_server_sockfd = bind_unix_domain_socket(sockpath.c_str(), 00700);
-  if (process_server_sockfd == -1) {
-    mgmt_fatal(errno, "[LocalManager::initMgmtProcessServer] failed to bind socket at %s\n", sockpath.c_str());
-  }
-
-#if HAVE_EVENTFD
-  wakeup_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
-  if (wakeup_fd < 0) {
-    mgmt_fatal(errno, "[LocalManager::initMgmtProcessServer] failed to create eventfd. errno : %s\n", strerror(errno));
-  }
-#endif
-
-  umask(oldmask);
-  RecSetRecordInt("proxy.node.restarts.manager.start_time", manager_started_at, REC_SOURCE_DEFAULT);
-}
-
-/*
- * pollMgmtProcessServer()
- * -  Function checks the mgmt process server for new processes
- *    and any requests sent from processes. It handles processes sent.
- */
-void
-LocalManager::pollMgmtProcessServer()
-{
-  struct timeval timeout;
-  fd_set fdlist;
-
-  while (true) {
-#if TS_HAS_WCCP
-    int wccp_fd = wccp_cache.getSocket();
-#endif
-
-    timeout.tv_sec  = process_server_timeout_secs;
-    timeout.tv_usec = process_server_timeout_msecs * 1000;
-
-    FD_ZERO(&fdlist);
-
-    if (process_server_sockfd != ts::NO_FD) {
-      FD_SET(process_server_sockfd, &fdlist);
-    }
-
-    if (watched_process_fd != ts::NO_FD) {
-      FD_SET(watched_process_fd, &fdlist);
-    }
-
-#if TS_HAS_WCCP
-    // Only run WCCP housekeeping while we have a server process.
-    // Note: The WCCP socket is opened iff WCCP is configured.
-    if (wccp_fd != ts::NO_FD && watched_process_fd != ts::NO_FD) {
-      wccp_cache.housekeeping();
-      time_t wccp_wait = wccp_cache.waitTime();
-      if (wccp_wait < process_server_timeout_secs)
-        timeout.tv_sec = wccp_wait;
-
-      if (wccp_fd != ts::NO_FD) {
-        FD_SET(wccp_fd, &fdlist);
-      }
-    }
-#endif
-
-#if HAVE_EVENTFD
-    if (wakeup_fd != ts::NO_FD) {
-      FD_SET(wakeup_fd, &fdlist);
-    }
-#endif
-
-    int num = mgmt_select(FD_SETSIZE, &fdlist, nullptr, nullptr, &timeout);
-
-    switch (num) {
-    case 0:
-      // Timed out, nothing to do.
-      return;
-    case -1:
-      if (mgmt_transient_error()) {
-        continue;
-      }
-
-      mgmt_log("[LocalManager::pollMgmtProcessServer] select failed: %s (%d)\n", ::strerror(errno), errno);
-      return;
-
-    default:
-      // if we get a wakeup_fd event, we may not want to follow it
-      // because there may be more data to be read on the socket.
-      bool keep_polling = false;
-#if TS_HAS_WCCP
-      if (wccp_fd != ts::NO_FD && FD_ISSET(wccp_fd, &fdlist)) {
-        wccp_cache.handleMessage();
-        --num;
-        keep_polling = true;
-      }
-#endif
-
-      if (process_server_sockfd != ts::NO_FD && FD_ISSET(process_server_sockfd, &fdlist)) { /* New connection */
-        struct sockaddr_in clientAddr;
-        socklen_t clientLen = sizeof(clientAddr);
-        int new_sockfd      = mgmt_accept(process_server_sockfd, reinterpret_cast<struct sockaddr *>(&clientAddr), &clientLen);
-
-        mgmt_log("[LocalManager::pollMgmtProcessServer] New process connecting fd '%d'\n", new_sockfd);
-
-        if (new_sockfd < 0) {
-          mgmt_elog(errno, "[LocalManager::pollMgmtProcessServer] ==> ");
-        } else if (!processRunning()) {
-          watched_process_fd = new_sockfd;
-        } else {
-          close_socket(new_sockfd);
-        }
-        --num;
-        keep_polling = true;
-      }
-
-      if (ts::NO_FD != watched_process_fd && FD_ISSET(watched_process_fd, &fdlist)) {
-        int res;
-        MgmtMessageHdr mh_hdr;
-
-        keep_polling = true;
-
-        // read the message
-        if ((res = mgmt_read_pipe(watched_process_fd, reinterpret_cast<char *>(&mh_hdr), sizeof(MgmtMessageHdr))) > 0) {
-          MgmtMessageHdr *mh_full = static_cast<MgmtMessageHdr *>(malloc(sizeof(MgmtMessageHdr) + mh_hdr.data_len));
-          memcpy(mh_full, &mh_hdr, sizeof(MgmtMessageHdr));
-          char *data_raw = reinterpret_cast<char *>(mh_full) + sizeof(MgmtMessageHdr);
-          if ((res = mgmt_read_pipe(watched_process_fd, data_raw, mh_hdr.data_len)) > 0) {
-            handleMgmtMsgFromProcesses(mh_full);
-          } else if (res < 0) {
-            mgmt_fatal(0, "[LocalManager::pollMgmtProcessServer] Error in read (errno: %d)\n", -res);
-          }
-          free(mh_full);
-        } else if (res < 0) {
-          mgmt_fatal(0, "[LocalManager::pollMgmtProcessServer] Error in read (errno: %d)\n", -res);
-        }
-
-        // handle EOF
-        if (res == 0) {
-          int estatus;
-          pid_t tmp_pid = watched_process_pid;
-
-          Debug("lm", "[LocalManager::pollMgmtProcessServer] Lost process EOF!");
-
-          close_socket(watched_process_fd);
-
-          waitpid(watched_process_pid, &estatus, 0); /* Reap child */
-          if (WIFSIGNALED(estatus)) {
-            int sig = WTERMSIG(estatus);
-            mgmt_log("[LocalManager::pollMgmtProcessServer] Server Process terminated due to Sig %d: %s\n", sig, strsignal(sig));
-          } else if (WIFEXITED(estatus)) {
-            int return_code = WEXITSTATUS(estatus);
-
-            // traffic_server's exit code will be UNRECOVERABLE_EXIT if it calls
-            // ink_emergency() or ink_emergency_va(). The call signals that traffic_server
-            // cannot be recovered with a reboot. In other words, catastrophic failure.
-            if (return_code == UNRECOVERABLE_EXIT) {
-              proxy_recoverable = false;
-            }
-          }
-
-          if (lmgmt->run_proxy) {
-            mgmt_log("[Alarms::signalAlarm] Server Process was reset\n");
-            lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_PROCESS_DIED, nullptr);
-          } else {
-            mgmt_log("[TrafficManager] Server process shutdown\n");
-          }
-
-          watched_process_fd = watched_process_pid = -1;
-          if (tmp_pid != -1) { /* Incremented after a pid: message is sent */
-            proxy_running--;
-          }
-          proxy_started_at = -1;
-          RecSetRecordInt("proxy.node.proxy_running", 0, REC_SOURCE_DEFAULT);
-        }
-
-        --num;
-      }
-
-#if HAVE_EVENTFD
-      if (wakeup_fd != ts::NO_FD && FD_ISSET(wakeup_fd, &fdlist)) {
-        if (!keep_polling) {
-          // read or else fd will always be set.
-          uint64_t ignore;
-          ATS_UNUSED_RETURN(read(wakeup_fd, &ignore, sizeof(uint64_t)));
-          return;
-        }
-        --num;
-      }
-#else
-      (void)keep_polling; // suppress compiler warning
-#endif
-
-      ink_assert(num == 0); /* Invariant */
-    }
-  }
-}
-
-void
-LocalManager::handleMgmtMsgFromProcesses(MgmtMessageHdr *mh)
-{
-  char *data_raw = reinterpret_cast<char *>(mh) + sizeof(MgmtMessageHdr);
-  switch (mh->msg_id) {
-  case MGMT_SIGNAL_PID:
-    watched_process_pid = *(reinterpret_cast<pid_t *>(data_raw));
-    lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_PROCESS_BORN, nullptr);
-    proxy_running++;
-    proxy_launch_pid         = -1;
-    proxy_launch_outstanding = false;
-    RecSetRecordInt("proxy.node.proxy_running", 1, REC_SOURCE_DEFAULT);
-    break;
-
-  // FIX: This is very messy need to correlate mgmt signals and
-  // alarms better
-  case MGMT_SIGNAL_CONFIG_ERROR:
-    alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_CONFIG_ERROR, data_raw);
-    break;
-  case MGMT_SIGNAL_SYSTEM_ERROR:
-    alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_SYSTEM_ERROR, data_raw);
-    break;
-  case MGMT_SIGNAL_CACHE_ERROR:
-    alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_CACHE_ERROR, data_raw);
-    break;
-  case MGMT_SIGNAL_CACHE_WARNING:
-    alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_CACHE_WARNING, data_raw);
-    break;
-  case MGMT_SIGNAL_LOGGING_ERROR:
-    alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_LOGGING_ERROR, data_raw);
-    break;
-  case MGMT_SIGNAL_LOGGING_WARNING:
-    alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_LOGGING_WARNING, data_raw);
-    break;
-  case MGMT_SIGNAL_PLUGIN_SET_CONFIG: {
-    char var_name[256];
-    char var_value[256];
-    MgmtType stype;
-    // stype is an enum type, so cast to an int* to avoid warnings. /leif
-    int tokens = sscanf(data_raw, "%255s %d %255s", var_name, reinterpret_cast<int *>(&stype), var_value);
-    if (tokens != 3) {
-      stype = MGMT_INVALID;
-    }
-    switch (stype) {
-    case MGMT_INT:
-      RecSetRecordInt(var_name, ink_atoi64(var_value), REC_SOURCE_EXPLICIT);
-      break;
-    case MGMT_COUNTER:
-    case MGMT_FLOAT:
-    case MGMT_STRING:
-    case MGMT_INVALID:
-    default:
-      mgmt_log("[LocalManager::handleMgmtMsgFromProcesses] "
-               "Invalid plugin set-config msg '%s'\n",
-               data_raw);
-      break;
-    }
-  } break;
-  case MGMT_SIGNAL_LIBRECORDS:
-    if (mh->data_len > 0) {
-      executeMgmtCallback(MGMT_SIGNAL_LIBRECORDS, {data_raw, static_cast<size_t>(mh->data_len)});
-    } else {
-      executeMgmtCallback(MGMT_SIGNAL_LIBRECORDS, {});
-    }
-    break;
-  case MGMT_SIGNAL_CONFIG_FILE_CHILD: {
-    static const MgmtMarshallType fields[] = {MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING};
-    char *parent                           = nullptr;
-    char *child                            = nullptr;
-    if (mgmt_message_parse(data_raw, mh->data_len, fields, countof(fields), &parent, &child) != -1) {
-      configFiles->configFileChild(parent, child);
-    } else {
-      mgmt_log("[LocalManager::handleMgmtMsgFromProcesses] "
-               "MGMT_SIGNAL_CONFIG_FILE_CHILD mgmt_message_parse error\n");
-    }
-    // Output pointers are guaranteed to be NULL or valid.
-    ats_free_null(parent);
-    ats_free_null(child);
-  } break;
-
-  default:
-    break;
-  }
-}
-
-void
-LocalManager::sendMgmtMsgToProcesses(int msg_id, const char *data_str)
-{
-  sendMgmtMsgToProcesses(msg_id, data_str, strlen(data_str) + 1);
-  return;
-}
-
-void
-LocalManager::sendMgmtMsgToProcesses(int msg_id, const char *data_raw, int data_len)
-{
-  MgmtMessageHdr *mh;
-
-  mh           = static_cast<MgmtMessageHdr *>(alloca(sizeof(MgmtMessageHdr) + data_len));
-  mh->msg_id   = msg_id;
-  mh->data_len = data_len;
-  memcpy(reinterpret_cast<char *>(mh) + sizeof(MgmtMessageHdr), data_raw, data_len);
-  sendMgmtMsgToProcesses(mh);
-  return;
-}
-
-void
-LocalManager::sendMgmtMsgToProcesses(MgmtMessageHdr *mh)
-{
-  switch (mh->msg_id) {
-  case MGMT_EVENT_SHUTDOWN: {
-    run_proxy = false;
-    this->closeProxyPorts();
-    break;
-  }
-  case MGMT_EVENT_RESTART:
-    run_proxy = true;
-    listenForProxy();
-    return;
-  case MGMT_EVENT_BOUNCE: /* Just bouncing the cluster, have it exit well restart */
-    mh->msg_id = MGMT_EVENT_SHUTDOWN;
-    break;
-  case MGMT_EVENT_ROLL_LOG_FILES:
-    mgmt_log("[LocalManager::SendMgmtMsgsToProcesses]Event is being constructed .\n");
-    break;
-  case MGMT_EVENT_CONFIG_FILE_UPDATE:
-    bool found;
-    char *fname = nullptr;
-    ConfigManager *rb;
-    char *data_raw;
-
-    data_raw = reinterpret_cast<char *>(mh) + sizeof(MgmtMessageHdr);
-    fname    = REC_readString(data_raw, &found);
-
-    RecT rec_type;
-    if (RecGetRecordType(data_raw, &rec_type) == REC_ERR_OKAY && rec_type == RECT_CONFIG) {
-      RecSetSyncRequired(data_raw);
-    } else {
-      mgmt_log("[LocalManager:sendMgmtMsgToProcesses] Unknown file change: '%s'\n", data_raw);
-    }
-    ink_assert(found);
-    if (!(fname && configFiles && configFiles->getConfigObj(fname, &rb)) &&
-        (strcmp(data_raw, "proxy.config.body_factory.template_sets_dir") != 0) &&
-        (strcmp(data_raw, "proxy.config.ssl.server.ticket_key.filename") != 0)) {
-      mgmt_fatal(0, "[LocalManager::sendMgmtMsgToProcesses] "
-                    "Invalid 'data_raw' for MGMT_EVENT_CONFIG_FILE_UPDATE\n");
-    }
-    ats_free(fname);
-    break;
-  }
-
-  if (watched_process_fd != -1) {
-    if (mgmt_write_pipe(watched_process_fd, reinterpret_cast<char *>(mh), sizeof(MgmtMessageHdr) + mh->data_len) <= 0) {
-      // In case of Linux, sometimes when the TS dies, the connection between TS and TM
-      // is not closed properly. the socket does not receive an EOF. So, the TM does
-      // not detect that the connection and hence TS has gone down. Hence it still
-      // tries to send a message to TS, but encounters an error and enters here
-      // Also, ensure that this whole thing is done only once because there will be a
-      // deluge of message in the traffic.log otherwise
-
-      static pid_t check_prev_pid    = watched_process_pid;
-      static pid_t check_current_pid = watched_process_pid;
-      if (check_prev_pid != watched_process_pid) {
-        check_prev_pid    = watched_process_pid;
-        check_current_pid = watched_process_pid;
-      }
-
-      if (check_prev_pid == check_current_pid) {
-        check_current_pid = -1;
-        int lerrno        = errno;
-        mgmt_elog(errno, "[LocalManager::sendMgmtMsgToProcesses] Error writing message\n");
-        if (lerrno == ECONNRESET || lerrno == EPIPE) { // Connection closed by peer or Broken pipe
-          if ((kill(watched_process_pid, 0) < 0) && (errno == ESRCH)) {
-            // TS is down
-            pid_t tmp_pid = watched_process_pid;
-            close_socket(watched_process_fd);
-            mgmt_log("[LocalManager::pollMgmtProcessServer] "
-                     "Server Process has been terminated\n");
-            if (lmgmt->run_proxy) {
-              mgmt_log("[Alarms::signalAlarm] Server Process was reset\n");
-              lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_PROCESS_DIED, nullptr);
-            } else {
-              mgmt_log("[TrafficManager] Server process shutdown\n");
-            }
-            watched_process_fd = watched_process_pid = -1;
-            if (tmp_pid != -1) { /* Incremented after a pid: message is sent */
-              proxy_running--;
-            }
-            proxy_started_at = -1;
-            RecSetRecordInt("proxy.node.proxy_running", 0, REC_SOURCE_DEFAULT);
-            // End of TS down
-          } else {
-            // TS is still up, but the connection is lost
-            const char *err_msg = "The TS-TM connection is broken for some reason. Either restart TS and TM or correct this error "
-                                  "for TM to display TS statistics correctly";
-            lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_PROXY_SYSTEM_ERROR, err_msg);
-          }
-
-          // check if the TS is down, by checking the pid
-          // if TS is down, then,
-          //     raise an alarm
-          //     set the variables so that TS is restarted later
-          // else (TS is still up)
-          //     raise an alarm stating the problem
-        }
-      }
-    }
-  }
-}
-
-void
-LocalManager::signalFileChange(const char *var_name)
-{
-  signalEvent(MGMT_EVENT_CONFIG_FILE_UPDATE, var_name);
-
-  return;
-}
-
-void
-LocalManager::signalEvent(int msg_id, const char *data_str)
-{
-  signalEvent(msg_id, data_str, strlen(data_str) + 1);
-  return;
-}
-
-void
-LocalManager::signalEvent(int msg_id, const char *data_raw, int data_len)
-{
-  MgmtMessageHdr *mh;
-  size_t n = sizeof(MgmtMessageHdr) + data_len;
-
-  mh           = static_cast<MgmtMessageHdr *>(ats_malloc(n));
-  mh->msg_id   = msg_id;
-  mh->data_len = data_len;
-  auto payload = mh->payload();
-  memcpy(payload.data(), data_raw, data_len);
-  this->enqueue(mh);
-  //  ink_assert(enqueue(mgmt_event_queue, mh));
-
-#if HAVE_EVENTFD
-  // we don't care about the actual value of wakeup_fd, so just keep adding 1. just need to
-  // wakeup the fd. also, note that wakeup_fd was initialized to non-blocking so we can
-  // directly write to it without any timeout checking.
-  //
-  // don't trigger if MGMT_EVENT_LIBRECORD because they happen all the time
-  // and don't require a quick response. for MGMT_EVENT_LIBRECORD, rely on timeouts so
-  // traffic_server can spend more time doing other things
-  uint64_t one = 1;
-  if (wakeup_fd != ts::NO_FD && mh->msg_id != MGMT_EVENT_LIBRECORDS) {
-    ATS_UNUSED_RETURN(write(wakeup_fd, &one, sizeof(uint64_t))); // trigger to stop polling
-  }
-#endif
-}
-
-/*
- * processEventQueue()
- *   Function drains and processes the mgmt event queue
- * notifying any registered callback functions and performing
- * any mgmt tasks for each event.
- */
-void
-LocalManager::processEventQueue()
-{
-  while (!this->queue_empty()) {
-    bool handled_by_mgmt = false;
-
-    MgmtMessageHdr *mh = this->dequeue();
-    auto payload       = mh->payload().rebind<char>();
-
-    // check if we have a local file update
-    if (mh->msg_id == MGMT_EVENT_CONFIG_FILE_UPDATE) {
-      // records.config
-      if (!(strcmp(payload.begin(), ts::filename::RECORDS))) {
-        if (RecReadConfigFile() != REC_ERR_OKAY) {
-          mgmt_elog(errno, "[fileUpdated] Config update failed for %s\n", ts::filename::RECORDS);
-        } else {
-          RecConfigWarnIfUnregistered();
-        }
-        handled_by_mgmt = true;
-      }
-    }
-
-    if (!handled_by_mgmt) {
-      if (processRunning() == false) {
-        // Fix INKqa04984
-        // If traffic server hasn't completely come up yet,
-        // we will hold off until next round.
-        this->enqueue(mh);
-        return;
-      }
-      Debug("lm", "[TrafficManager] ==> Sending signal event '%d' %s payload=%d", mh->msg_id, payload.begin(), int(payload.size()));
-      lmgmt->sendMgmtMsgToProcesses(mh);
-    }
-    ats_free(mh);
-  }
-}
-
-/*
- * startProxy()
- *   Function fires up a proxy process.
- *
- * Args:
- *   onetime_options: one time options that traffic_server should be started with (ie
- *                    these options do not persist across reboots)
- */
-static const size_t OPTIONS_SIZE = 16384; // Arbitrary max size for command line option string
-
-bool
-LocalManager::startProxy(const char *onetime_options)
-{
-  if (proxy_launch_outstanding) {
-    return false;
-  }
-  mgmt_log("[LocalManager::startProxy] Launching ts process\n");
-
-  pid_t pid;
-
-  // Before we do anything lets check for the existence of
-  // the traffic server binary along with it's execute permissions
-  if (access(absolute_proxy_binary, F_OK) < 0) {
-    // Error can't find traffic_server
-    mgmt_elog(errno, "[LocalManager::startProxy] Unable to find traffic server at %s\n", absolute_proxy_binary);
-    return false;
-  }
-  // traffic server binary exists, check permissions
-  else if (access(absolute_proxy_binary, R_OK | X_OK) < 0) {
-    // Error don't have proper permissions
-    mgmt_elog(errno, "[LocalManager::startProxy] Unable to access %s due to bad permissions \n", absolute_proxy_binary);
-    return false;
-  }
-
-  if (env_prep) {
-#ifdef POSIX_THREAD
-    if ((pid = fork()) < 0)
-#else
-    if ((pid = fork1()) < 0)
-#endif
-    {
-      mgmt_elog(errno, "[LocalManager::startProxy] Unable to fork1 prep process\n");
-      return false;
-    } else if (pid > 0) {
-      int estatus;
-      waitpid(pid, &estatus, 0);
-    } else {
-      int res;
-
-      char env_prep_bin[MAXPATHLEN];
-      std::string bindir(RecConfigReadBinDir());
-
-      ink_filepath_make(env_prep_bin, sizeof(env_prep_bin), bindir.c_str(), env_prep);
-      res = execl(env_prep_bin, env_prep_bin, (char *)nullptr);
-      _exit(res);
-    }
-  }
-#ifdef POSIX_THREAD
-  if ((pid = fork()) < 0)
-#else
-  if ((pid = fork1()) < 0)
-#endif
-  {
-    mgmt_elog(errno, "[LocalManager::startProxy] Unable to fork1 process\n");
-    return false;
-  } else if (pid > 0) { /* Parent */
-    proxy_launch_pid         = pid;
-    proxy_launch_outstanding = true;
-    proxy_started_at         = time(nullptr);
-    ++proxy_launch_count;
-    RecSetRecordInt("proxy.node.restarts.proxy.start_time", proxy_started_at, REC_SOURCE_DEFAULT);
-    RecSetRecordInt("proxy.node.restarts.proxy.restart_count", proxy_launch_count, REC_SOURCE_DEFAULT);
-  } else {
-    int i = 0;
-    char *options[32], *last, *tok;
-    char options_buffer[OPTIONS_SIZE];
-    ts::FixedBufferWriter w{options_buffer, OPTIONS_SIZE};
-
-    w.clip(1);
-    w.print("{}{}", ts::bwf::OptionalAffix(proxy_options), ts::bwf::OptionalAffix(onetime_options));
-
-    // Make sure we're starting the proxy in mgmt mode
-    if (w.view().find(MGMT_OPT) == std::string_view::npos) {
-      w.write(MGMT_OPT);
-      w.write(' ');
-    }
-
-    // pass the runroot option to traffic_server
-    std::string_view runroot_arg = get_runroot();
-    if (!runroot_arg.empty()) {
-      w.write(RUNROOT_OPT);
-      w.write(runroot_arg);
-      w.write(' ');
-    }
-
-    // Pass down port/fd information to traffic_server if there are any open ports.
-    if (std::any_of(m_proxy_ports.begin(), m_proxy_ports.end(), [](HttpProxyPort &p) { return ts::NO_FD != p.m_fd; })) {
-      char portbuf[128];
-      bool need_comma_p = false;
-
-      w.write("--httpport "sv);
-      for (auto &p : m_proxy_ports) {
-        if (ts::NO_FD != p.m_fd) {
-          if (need_comma_p) {
-            w.write(',');
-          }
-          need_comma_p = true;
-          p.print(portbuf, sizeof(portbuf));
-          w.write(portbuf);
-        }
-      }
-    }
-
-    w.extend(1);
-    w.write('\0'); // null terminate.
-
-    Debug("lm", "[LocalManager::startProxy] Launching %s '%s'", absolute_proxy_binary, w.data());
-
-    // Unfortunately the normally obnoxious null writing of strtok is in this case a required
-    // side effect and other alternatives are noticeably more clunky.
-    ink_zero(options);
-    options[0] = absolute_proxy_binary;
-    i          = 1;
-    tok        = strtok_r(options_buffer, " ", &last);
-    Debug("lm", "opt %d = '%s'", i, tok);
-    options[i++] = tok;
-    while (i < 32 && (tok = strtok_r(nullptr, " ", &last))) {
-      Debug("lm", "opt %d = '%s'", i, tok);
-      options[i++] = tok;
-    }
-
-    EnableDeathSignal(SIGTERM);
-
-    execv(absolute_proxy_binary, options);
-    mgmt_fatal(errno, "[LocalManager::startProxy] Exec of %s failed\n", absolute_proxy_binary);
-  }
-  return true;
-}
-
-/** Close all open ports.
- */
-void
-LocalManager::closeProxyPorts()
-{
-  for (auto &p : lmgmt->m_proxy_ports) {
-    if (ts::NO_FD != p.m_fd) {
-      close_socket(p.m_fd);
-      p.m_fd = ts::NO_FD;
-    }
-  }
-}
-/*
- * listenForProxy()
- *  Function listens on the accept port of the proxy, so users aren't dropped.
- */
-void
-LocalManager::listenForProxy()
-{
-  if (!run_proxy || !listen_for_proxy) {
-    return;
-  }
-
-  // We are not already bound, bind the port
-  for (auto &p : lmgmt->m_proxy_ports) {
-    if (ts::NO_FD == p.m_fd) {
-      // Check the protocol (TCP or UDP) and create an appropriate socket
-      if (p.isQUIC()) {
-        this->bindUdpProxyPort(p);
-      } else {
-        this->bindTcpProxyPort(p);
-      }
-    }
-
-    std::string_view fam{ats_ip_family_name(p.m_family)};
-    if (p.isQUIC()) {
-      // Can we do something like listen backlog for QUIC(UDP) ??
-      // Do nothing for now
-    } else {
-      // read backlog configuration value and overwrite the default value if found
-      bool found;
-      RecInt backlog = REC_readInteger("proxy.config.net.listen_backlog", &found);
-      backlog        = (found && backlog >= 0) ? backlog : ats_tcp_somaxconn();
-
-      if ((listen(p.m_fd, backlog)) < 0) {
-        mgmt_fatal(errno, "[LocalManager::listenForProxy] Unable to listen on port: %d (%.*s)\n", p.m_port, fam.size(), fam.data());
-      }
-    }
-
-    mgmt_log("[LocalManager::listenForProxy] Listening on port: %d (%.*s)\n", p.m_port, fam.size(), fam.data());
-  }
-  return;
-}
-
-/*
- * bindUdpProxyPort()
- *  Function binds the accept port of the proxy
- */
-void
-LocalManager::bindUdpProxyPort(HttpProxyPort &port)
-{
-  int one  = 1;
-  int priv = (port.m_port < 1024 && 0 != geteuid()) ? ElevateAccess::LOW_PORT_PRIVILEGE : 0;
-
-  ElevateAccess access(priv);
-
-  if ((port.m_fd = socket(port.m_family, SOCK_DGRAM, 0)) < 0) {
-    mgmt_fatal(0, "[bindProxyPort] Unable to create socket : %s\n", strerror(errno));
-  }
-
-  if (port.m_family == AF_INET6) {
-    if (setsockopt(port.m_fd, IPPROTO_IPV6, IPV6_V6ONLY, SOCKOPT_ON, sizeof(int)) < 0) {
-      mgmt_log("[bindProxyPort] Unable to set socket options: %d : %s\n", port.m_port, strerror(errno));
-    }
-  }
-  if (setsockopt(port.m_fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&one), sizeof(int)) < 0) {
-    mgmt_fatal(0, "[bindProxyPort] Unable to set socket options: %d : %s\n", port.m_port, strerror(errno));
-  }
-
-  IpEndpoint ip;
-  if (port.m_inbound_ip.isValid()) {
-    ip.assign(port.m_inbound_ip);
-  } else if (AF_INET6 == port.m_family) {
-    if (m_inbound_ip6.isValid()) {
-      ip.assign(m_inbound_ip6);
-    } else {
-      ip.setToAnyAddr(AF_INET6);
-    }
-  } else if (AF_INET == port.m_family) {
-    if (m_inbound_ip4.isValid()) {
-      ip.assign(m_inbound_ip4);
-    } else {
-      ip.setToAnyAddr(AF_INET);
-    }
-  } else {
-    mgmt_fatal(0, "[bindProxyPort] Proxy port with invalid address type %d\n", port.m_family);
-  }
-  ip.network_order_port() = htons(port.m_port);
-  if (bind(port.m_fd, &ip.sa, ats_ip_size(&ip)) < 0) {
-    mgmt_fatal(0, "[bindProxyPort] Unable to bind socket: %d : %s\n", port.m_port, strerror(errno));
-  }
-
-  Debug("lm", "[bindProxyPort] Successfully bound proxy port %d", port.m_port);
-}
-
-/*
- * bindTcpProxyPort()
- *  Function binds the accept port of the proxy
- */
-void
-LocalManager::bindTcpProxyPort(HttpProxyPort &port)
-{
-  int one  = 1;
-  int priv = (port.m_port < 1024 && 0 != geteuid()) ? ElevateAccess::LOW_PORT_PRIVILEGE : 0;
-
-  ElevateAccess access(priv);
-
-  /* Setup reliable connection, for large config changes */
-  if ((port.m_fd = socket(port.m_family, SOCK_STREAM, 0)) < 0) {
-    mgmt_fatal(0, "[bindProxyPort] Unable to create socket : %s\n", strerror(errno));
-  }
-
-  if (port.m_type == HttpProxyPort::TRANSPORT_DEFAULT) {
-    int should_filter_int = 0;
-    bool found;
-    should_filter_int = REC_readInteger("proxy.config.net.defer_accept", &found);
-    if (found && should_filter_int > 0) {
-#if defined(SOL_FILTER) && defined(FIL_ATTACH)
-      (void)setsockopt(port.m_fd, SOL_FILTER, FIL_ATTACH, "httpfilt", 9);
-#endif
-    }
-  }
-
-  if (port.m_mptcp) {
-#if MPTCP_ENABLED
-    int err;
-
-    err = setsockopt(port.m_fd, IPPROTO_TCP, MPTCP_ENABLED, &one, sizeof(one));
-    if (err < 0) {
-      mgmt_log("[bindProxyPort] Unable to enable MPTCP: %s\n", strerror(errno));
-      Debug("lm_mptcp", "[bindProxyPort] Unable to enable MPTCP: %s", strerror(errno));
-    } else {
-      mgmt_log("[bindProxyPort] Successfully enabled MPTCP on %d\n", port.m_port);
-      Debug("lm_mptcp", "[bindProxyPort] Successfully enabled MPTCP on %d\n", port.m_port);
-    }
-#else
-    Debug("lm_mptcp", "[bindProxyPort] Multipath TCP requested but not configured on this host");
-#endif
-  }
-
-  if (port.m_family == AF_INET6) {
-    if (setsockopt(port.m_fd, IPPROTO_IPV6, IPV6_V6ONLY, SOCKOPT_ON, sizeof(int)) < 0) {
-      mgmt_log("[bindProxyPort] Unable to set socket options: %d : %s\n", port.m_port, strerror(errno));
-    }
-  }
-  if (setsockopt(port.m_fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&one), sizeof(int)) < 0) {
-    mgmt_fatal(0, "[bindProxyPort] Unable to set socket options: %d : %s\n", port.m_port, strerror(errno));
-  }
-
-  if (port.m_proxy_protocol) {
-    Debug("lm", "[bindProxyPort] Proxy Protocol enabled");
-  }
-
-  if (port.m_inbound_transparent_p) {
-#if TS_USE_TPROXY
-    Debug("http_tproxy", "Listen port %d inbound transparency enabled.", port.m_port);
-    if (setsockopt(port.m_fd, SOL_IP, TS_IP_TRANSPARENT, &one, sizeof(one)) == -1) {
-      mgmt_fatal(0, "[bindProxyPort] Unable to set transparent socket option [%d] %s\n", errno, strerror(errno));
-    }
-#else
-    Debug("lm", "[bindProxyPort] Transparency requested but TPROXY not configured");
-#endif
-  }
-
-  IpEndpoint ip;
-  if (port.m_inbound_ip.isValid()) {
-    ip.assign(port.m_inbound_ip);
-  } else if (AF_INET6 == port.m_family) {
-    if (m_inbound_ip6.isValid()) {
-      ip.assign(m_inbound_ip6);
-    } else {
-      ip.setToAnyAddr(AF_INET6);
-    }
-  } else if (AF_INET == port.m_family) {
-    if (m_inbound_ip4.isValid()) {
-      ip.assign(m_inbound_ip4);
-    } else {
-      ip.setToAnyAddr(AF_INET);
-    }
-  } else {
-    mgmt_fatal(0, "[bindProxyPort] Proxy port with invalid address type %d\n", port.m_family);
-  }
-  ip.network_order_port() = htons(port.m_port);
-  if (bind(port.m_fd, &ip.sa, ats_ip_size(&ip)) < 0) {
-    mgmt_fatal(0, "[bindProxyPort] Unable to bind socket: %d : %s\n", port.m_port, strerror(errno));
-  }
-
-  Debug("lm", "[bindProxyPort] Successfully bound proxy port %d", port.m_port);
-}
-
-void
-LocalManager::signalAlarm(int alarm_id, const char *desc, const char *ip)
-{
-  if (alarm_keeper) {
-    alarm_keeper->signalAlarm(static_cast<alarm_t>(alarm_id), desc, ip);
-  }
-}
diff --git a/mgmt/LocalManager.h b/mgmt/LocalManager.h
deleted file mode 100644
index f5121a4..0000000
--- a/mgmt/LocalManager.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/** @file
-
-  Definitions for the LocalManager class.
-
-  @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.
- */
-
-#pragma once
-
-#include <string>
-
-#include "BaseManager.h"
-#include "records/I_RecHttp.h"
-#include "tscore/I_Version.h"
-
-#include <syslog.h>
-#if TS_HAS_WCCP
-#include <wccp/Wccp.h>
-#endif
-#if HAVE_EVENTFD
-#include <sys/eventfd.h>
-#endif
-
-class Alarms;
-class FileManager;
-
-enum ManagementPendingOperation {
-  MGMT_PENDING_NONE,         // Do nothing
-  MGMT_PENDING_RESTART,      // Restart TS and TM
-  MGMT_PENDING_BOUNCE,       // Restart TS
-  MGMT_PENDING_STOP,         // Stop TS
-  MGMT_PENDING_DRAIN,        // Drain TS
-  MGMT_PENDING_IDLE_RESTART, // Restart TS and TM when TS is idle
-  MGMT_PENDING_IDLE_BOUNCE,  // Restart TS when TS is idle
-  MGMT_PENDING_IDLE_STOP,    // Stop TS when TS is idle
-  MGMT_PENDING_IDLE_DRAIN,   // Drain TS when TS is idle from new connections
-  MGMT_PENDING_UNDO_DRAIN,   // Recover TS from drain
-};
-
-class LocalManager : public BaseManager
-{
-public:
-  explicit LocalManager(bool proxy_on, bool listen);
-  ~LocalManager();
-
-  void initAlarm();
-  void initCCom(const AppVersionInfo &version, FileManager *files, int mcport, char *addr, int rsport);
-  void initMgmtProcessServer();
-  void pollMgmtProcessServer();
-  void handleMgmtMsgFromProcesses(MgmtMessageHdr *mh);
-  void sendMgmtMsgToProcesses(int msg_id, const char *data_str);
-  void sendMgmtMsgToProcesses(int msg_id, const char *data_raw, int data_len);
-  void sendMgmtMsgToProcesses(MgmtMessageHdr *mh);
-
-  void signalFileChange(const char *var_name);
-  void signalEvent(int msg_id, const char *data_str);
-  void signalEvent(int msg_id, const char *data_raw, int data_len);
-  void signalAlarm(int alarm_id, const char *desc = nullptr, const char *ip = nullptr);
-
-  void processEventQueue();
-  bool startProxy(const char *onetime_options);
-  void listenForProxy();
-  void bindUdpProxyPort(HttpProxyPort &);
-  void bindTcpProxyPort(HttpProxyPort &);
-  void closeProxyPorts();
-
-  void mgmtCleanup();
-  void mgmtShutdown();
-  void processShutdown(bool mainThread = false);
-  void processRestart();
-  void processBounce();
-  void processDrain(int to_drain = 1);
-  void rollLogFiles();
-  void clearStats(const char *name = nullptr);
-  void hostStatusSetDown(const char *marshalled_req, int len);
-  void hostStatusSetUp(const char *marshalled_req, int len);
-
-  bool processRunning();
-
-  bool run_proxy;
-  bool listen_for_proxy;
-  bool proxy_recoverable = true; // false if traffic_server cannot recover with a reboot
-  time_t manager_started_at;
-  time_t proxy_started_at                              = -1;
-  int proxy_launch_count                               = 0;
-  bool proxy_launch_outstanding                        = false;
-  ManagementPendingOperation mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-  time_t mgmt_shutdown_triggered_at;
-  time_t mgmt_drain_triggered_at;
-  int proxy_running = 0;
-  HttpProxyPort::Group m_proxy_ports;
-  // Local inbound addresses to bind, if set.
-  IpAddr m_inbound_ip4;
-  IpAddr m_inbound_ip6;
-
-  int process_server_timeout_secs;
-  int process_server_timeout_msecs;
-
-  char *absolute_proxy_binary;
-  char *proxy_name;
-  char *proxy_binary;
-  std::string proxy_options; // These options should persist across proxy reboots
-  char *env_prep;
-
-  int process_server_sockfd = ts::NO_FD;
-  int watched_process_fd    = ts::NO_FD;
-#if HAVE_EVENTFD
-  int wakeup_fd = ts::NO_FD; // external trigger to stop polling
-#endif
-  pid_t proxy_launch_pid = -1;
-
-  Alarms *alarm_keeper     = nullptr;
-  FileManager *configFiles = nullptr;
-
-  pid_t watched_process_pid = -1;
-
-  int syslog_facility = LOG_DAEMON;
-
-#if TS_HAS_WCCP
-  wccp::Cache wccp_cache;
-#endif
-private:
-}; /* End class LocalManager */
-
-extern LocalManager *lmgmt;
diff --git a/mgmt/Makefile.am b/mgmt/Makefile.am
index b200946..c123a26 100644
--- a/mgmt/Makefile.am
+++ b/mgmt/Makefile.am
@@ -17,23 +17,22 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-SUBDIRS = utils api
+SUBDIRS = utils api config rpc
 
 # Decoder ring:
 # 	libmgmt_c.la 	Configuration records (defaults).
-# 	libmgmt_lm.la	libmgmt for Local Manager applications (traffic_manager)
 # 	libmgmt_p.la	libmgmt for Process Manager applications (everything else)
-noinst_LTLIBRARIES = libmgmt_c.la libmgmt_p.la libmgmt_lm.la
+noinst_LTLIBRARIES = libmgmt_c.la libmgmt_p.la
 
 AM_CPPFLAGS += \
 	$(iocore_include_dirs) \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
-	-I$(abs_top_srcdir)/mgmt/api/include \
 	-I$(abs_top_srcdir)/mgmt/utils \
 	-I$(abs_top_srcdir)/proxy \
 	-I$(abs_top_srcdir)/proxy/http \
 	-I$(abs_top_srcdir)/proxy/hdrs \
+        @SWOC_INCLUDES@ \
 	@YAMLCPP_INCLUDES@ \
 	$(TS_INCLUDES)
 
@@ -42,39 +41,16 @@
 	RecordsConfig.h
 
 libmgmt_COMMON = \
-	BaseManager.cc \
-	BaseManager.h \
 	MgmtDefs.h \
 	RecordsConfigUtils.cc
 
 libmgmt_p_la_SOURCES = \
 	$(libmgmt_COMMON) \
-	ProcessManager.cc \
-	ProcessManager.h \
 	ProxyConfig.cc \
 	ProxyConfig.h \
 	YamlCfg.cc \
 	YamlCfg.h
 
-libmgmt_lm_la_SOURCES = \
-	$(libmgmt_COMMON) \
-	Alarms.cc \
-	Alarms.h \
-	DerivativeMetrics.cc \
-	DerivativeMetrics.h \
-	FileManager.cc \
-	FileManager.h \
-	LocalManager.cc \
-	LocalManager.h \
-	ConfigManager.cc \
-	ConfigManager.h \
-	WebMgmtUtils.cc \
-	WebMgmtUtils.h
-
-libmgmt_lm_la_LIBADD = \
-	libmgmt_c.la \
-	$(top_builddir)/mgmt/utils/libutils_lm.la
-
 libmgmt_p_la_LIBADD = \
 	libmgmt_c.la \
 	$(top_builddir)/mgmt/utils/libutils_p.la
diff --git a/mgmt/MgmtDefs.h b/mgmt/MgmtDefs.h
index 2245ee5..41b76c2 100644
--- a/mgmt/MgmtDefs.h
+++ b/mgmt/MgmtDefs.h
@@ -30,7 +30,7 @@
 #include <string_view>
 
 #include "tscore/ink_defs.h"
-#include "tscpp/util/MemSpan.h"
+#include "swoc/MemSpan.h"
 #include "tscpp/util/TextView.h"
 
 typedef int64_t MgmtIntCounter;
@@ -39,19 +39,10 @@
 typedef float MgmtFloat;
 typedef char *MgmtString;
 
-enum MgmtType {
-  MGMT_INVALID  = -1,
-  MGMT_INT      = 0,
-  MGMT_FLOAT    = 1,
-  MGMT_STRING   = 2,
-  MGMT_COUNTER  = 3,
-  MGMT_TYPE_MAX = 4,
-};
-
 /// Management callback signature.
 /// The memory span is the message payload for the callback.
 /// This can be a lambda, which should be used if additional context information is needed.
-using MgmtCallback = std::function<void(ts::MemSpan<void>)>;
+using MgmtCallback = std::function<void(swoc::MemSpan<void>)>;
 
 //-------------------------------------------------------------------------
 // API conversion functions.
@@ -145,5 +136,3 @@
     store_string(_store_string)
 {
 }
-
-constexpr ts::TextView LM_CONNECTION_SERVER{"processerver.sock"};
diff --git a/mgmt/ProcessManager.cc b/mgmt/ProcessManager.cc
deleted file mode 100644
index b8661ee..0000000
--- a/mgmt/ProcessManager.cc
+++ /dev/null
@@ -1,512 +0,0 @@
-/** @file
-
-  File contains the member function defs and thread loop for the process manager.
-
-  @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 "InkAPIInternal.h"
-#include "ProcessManager.h"
-
-#include "tscore/ink_apidefs.h"
-#include "tscore/TSSystemState.h"
-#include "MgmtSocket.h"
-#include "tscore/I_Layout.h"
-
-/*
- * Global ProcessManager
- */
-ProcessManager *pmgmt = nullptr;
-
-// read_management_message attempts to read a message from the management
-// socket. Returns -errno on error, otherwise 0. If a message was read the
-// *msg pointer will be filled in with the message that was read.
-static int
-read_management_message(int sockfd, MgmtMessageHdr **msg)
-{
-  MgmtMessageHdr hdr;
-  int ret;
-
-  *msg = nullptr;
-
-  // We have a message, try to read the message header.
-  ret = mgmt_read_pipe(sockfd, reinterpret_cast<char *>(&hdr), sizeof(MgmtMessageHdr));
-  switch (ret) {
-  case 0:
-    // Received EOF.
-    return 0;
-  case sizeof(MgmtMessageHdr):
-    break;
-  default:
-    // Received -errno.
-    return -errno;
-  }
-
-  size_t msg_size          = sizeof(MgmtMessageHdr) + hdr.data_len;
-  MgmtMessageHdr *full_msg = static_cast<MgmtMessageHdr *>(ats_malloc(msg_size));
-
-  memcpy(full_msg, &hdr, sizeof(MgmtMessageHdr));
-  char *data_raw = reinterpret_cast<char *>(full_msg) + sizeof(MgmtMessageHdr);
-
-  ret = mgmt_read_pipe(sockfd, data_raw, hdr.data_len);
-  if (ret == 0) {
-    // Received EOF.
-    ats_free(full_msg);
-    return 0;
-  } else if (ret < 0) {
-    // Received -errno.
-    ats_free(full_msg);
-    return ret;
-  } else {
-    ink_release_assert(ret == hdr.data_len);
-    // Received the message.
-    *msg = full_msg;
-    return 0;
-  }
-}
-
-void
-ProcessManager::start(std::function<TSThread()> const &cb_init, std::function<void(TSThread)> const &cb_destroy)
-{
-  Debug("pmgmt", "starting process manager");
-
-  init    = cb_init;
-  destroy = cb_destroy;
-
-  ink_release_assert(running == 0);
-  ink_atomic_increment(&running, 1);
-  ink_thread_create(&poll_thread, processManagerThread, nullptr, 0, 0, nullptr);
-}
-
-void
-ProcessManager::stop()
-{
-  Debug("pmgmt", "stopping process manager");
-
-  ink_release_assert(running == 1);
-  ink_atomic_decrement(&running, 1);
-
-  int tmp;
-
-  if (local_manager_sockfd != ts::NO_FD) {
-    tmp                  = local_manager_sockfd;
-    local_manager_sockfd = ts::NO_FD;
-    close_socket(tmp);
-  }
-
-#if HAVE_EVENTFD
-  if (wakeup_fd != ts::NO_FD) {
-    tmp       = wakeup_fd;
-    wakeup_fd = ts::NO_FD;
-    close_socket(tmp);
-  }
-#endif
-
-  ink_thread_kill(poll_thread, SIGINT);
-
-  ink_thread_join(poll_thread);
-  poll_thread = ink_thread_null();
-
-  while (!queue_is_empty(mgmt_signal_queue)) {
-    char *sig = static_cast<char *>(::dequeue(mgmt_signal_queue));
-    ats_free(sig);
-  }
-
-  LLQ *tmp_queue    = mgmt_signal_queue;
-  mgmt_signal_queue = nullptr;
-  delete_queue(tmp_queue);
-}
-
-/*
- * processManagerThread(...)
- *   The start function and thread loop for the process manager.
- */
-void *
-ProcessManager::processManagerThread(void *arg)
-{
-  void *ret = arg;
-
-  while (!pmgmt) { /* Avert race condition, thread spun during constructor */
-    Debug("pmgmt", "waiting for initialization");
-    mgmt_sleep_sec(1);
-  }
-
-  if (pmgmt->require_lm) { /* Allow p. process to run w/o a lm */
-    pmgmt->initLMConnection();
-  } else {
-    return ret;
-  }
-
-  if (pmgmt->init) {
-    pmgmt->managerThread = pmgmt->init();
-  }
-
-  // Start pumping messages between the local process and the process
-  // manager. This will terminate when the process manager terminates
-  // or the local process calls stop(). In either case, it is likely
-  // that we will first notice because we got a socket error, but in
-  // the latter case, the `running` flag has already been toggled so
-  // we know that we are really doing a shutdown.
-  while (pmgmt->running) {
-    int ret;
-
-    if (pmgmt->require_lm) {
-      ret = pmgmt->pollLMConnection();
-      if (ret < 0 && pmgmt->running && !TSSystemState::is_event_system_shut_down()) {
-        Alert("exiting with read error from process manager: %s", strerror(-ret));
-      }
-    }
-
-    ret = pmgmt->processSignalQueue();
-    if (ret < 0 && pmgmt->running && !TSSystemState::is_event_system_shut_down()) {
-      Alert("exiting with write error from process manager: %s", strerror(-ret));
-    }
-  }
-
-  if (pmgmt->destroy && pmgmt->managerThread != nullptr) {
-    pmgmt->destroy(pmgmt->managerThread);
-    pmgmt->managerThread = nullptr;
-  }
-
-  return ret;
-}
-
-ProcessManager::ProcessManager(bool rlm)
-  : BaseManager(), require_lm(rlm), pid(getpid()), local_manager_sockfd(0), cbtable(nullptr), max_msgs_in_a_row(1)
-{
-  mgmt_signal_queue = create_queue();
-
-  local_manager_sockfd = ts::NO_FD;
-#if HAVE_EVENTFD
-  wakeup_fd = ts::NO_FD;
-#endif
-
-  // Set temp. process/manager timeout. Will be reconfigure later.
-  // Making the process_manager thread a spinning thread to start traffic server
-  // as quickly as possible. Will reset this timeout when reconfigure()
-  timeout = 0;
-}
-
-ProcessManager::~ProcessManager()
-{
-  if (running) {
-    stop();
-  }
-}
-
-void
-ProcessManager::reconfigure()
-{
-  max_msgs_in_a_row = MAX_MSGS_IN_A_ROW;
-
-  if (RecGetRecordInt("proxy.config.process_manager.timeout", &timeout) != REC_ERR_OKAY) {
-    // Default to 5sec if the timeout is unspecified.
-    timeout = 5;
-  }
-}
-
-void
-ProcessManager::signalConfigFileChild(const char *parent, const char *child)
-{
-  static const MgmtMarshallType fields[] = {MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING};
-
-  size_t len   = mgmt_message_length(fields, countof(fields), &parent, &child);
-  void *buffer = ats_malloc(len);
-
-  mgmt_message_marshall(buffer, len, fields, countof(fields), &parent, &child);
-  signalManager(MGMT_SIGNAL_CONFIG_FILE_CHILD, static_cast<const char *>(buffer), len);
-
-  ats_free(buffer);
-}
-
-void
-ProcessManager::signalManager(int msg_id, const char *data_str)
-{
-  signalManager(msg_id, data_str, strlen(data_str) + 1);
-}
-
-void
-ProcessManager::signalManager(int msg_id, const char *data_raw, int data_len)
-{
-  MgmtMessageHdr *mh;
-
-  mh           = static_cast<MgmtMessageHdr *>(ats_malloc(sizeof(MgmtMessageHdr) + data_len));
-  mh->msg_id   = msg_id;
-  mh->data_len = data_len;
-  memcpy(reinterpret_cast<char *>(mh) + sizeof(MgmtMessageHdr), data_raw, data_len);
-  this->signalManager(mh);
-}
-
-void
-ProcessManager::signalManager(int msg_id, std::string_view text)
-{
-  MgmtMessageHdr *mh;
-
-  // Make space for the extra null terminator.
-  mh           = static_cast<MgmtMessageHdr *>(ats_malloc(sizeof(MgmtMessageHdr) + text.size() + 1));
-  auto body    = reinterpret_cast<char *>(mh + 1); // start of the message body.
-  mh->msg_id   = msg_id;
-  mh->data_len = text.size() + 1;
-  memcpy(body, text.data(), text.size());
-  body[text.size()] = '\0';
-
-  this->signalManager(mh);
-}
-
-void
-ProcessManager::signalManager(MgmtMessageHdr *mh)
-{
-  if (!this->running) {
-    Warning("MgmtMessageHdr is ignored. Because ProcessManager is not running");
-    return;
-  }
-  ink_release_assert(::enqueue(mgmt_signal_queue, mh));
-
-#if HAVE_EVENTFD
-  // we don't care about the actual value of wakeup_fd, so just keep adding 1. just need to
-  // wakeup the fd. also, note that wakeup_fd was initialized to non-blocking so we can
-  // directly write to it without any timeout checking.
-  //
-  // don't trigger if MGMT_EVENT_LIBRECORD because they happen all the time
-  // and don't require a quick response. for MGMT_EVENT_LIBRECORD, rely on timeouts so
-  // traffic_server can spend more time doing other things/
-  uint64_t one = 1;
-  if (wakeup_fd != ts::NO_FD && mh->msg_id != MGMT_SIGNAL_LIBRECORDS) {
-    ATS_UNUSED_RETURN(write(wakeup_fd, &one, sizeof(uint64_t))); // trigger to stop polling
-  }
-#endif
-}
-
-int
-ProcessManager::processSignalQueue()
-{
-  while (!queue_is_empty(mgmt_signal_queue)) {
-    MgmtMessageHdr *mh = static_cast<MgmtMessageHdr *>(::dequeue(mgmt_signal_queue));
-
-    Debug("pmgmt", "signaling local manager with message ID %d", mh->msg_id);
-
-    if (require_lm) {
-      int ret = mgmt_write_pipe(local_manager_sockfd, reinterpret_cast<char *>(mh), sizeof(MgmtMessageHdr) + mh->data_len);
-      ats_free(mh);
-
-      if (ret < 0) {
-        return ret;
-      }
-    }
-  }
-
-  return 0;
-}
-
-void
-ProcessManager::initLMConnection()
-{
-  std::string rundir(RecConfigReadRuntimeDir());
-  std::string sockpath(Layout::relative_to(rundir, LM_CONNECTION_SERVER));
-
-  MgmtMessageHdr *mh_full;
-  int data_len;
-
-  int servlen;
-  struct sockaddr_un serv_addr;
-
-  if (sockpath.length() > sizeof(serv_addr.sun_path) - 1) {
-    errno = ENAMETOOLONG;
-    Fatal("Unable to create socket '%s': %s", sockpath.c_str(), strerror(errno));
-  }
-
-  /* Setup Connection to LocalManager */
-  memset(reinterpret_cast<char *>(&serv_addr), 0, sizeof(serv_addr));
-  serv_addr.sun_family = AF_UNIX;
-
-  ink_strlcpy(serv_addr.sun_path, sockpath.c_str(), sizeof(serv_addr.sun_path));
-#if defined(darwin) || defined(freebsd)
-  servlen = sizeof(sockaddr_un);
-#else
-  servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
-#endif
-
-  if ((local_manager_sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
-    Fatal("Unable to create socket '%s': %s", sockpath.c_str(), strerror(errno));
-  }
-
-  if (fcntl(local_manager_sockfd, F_SETFD, FD_CLOEXEC) < 0) {
-    Fatal("unable to set close-on-exec flag: %s", strerror(errno));
-  }
-
-  if ((connect(local_manager_sockfd, reinterpret_cast<struct sockaddr *>(&serv_addr), servlen)) < 0) {
-    Fatal("failed to connect management socket '%s': %s", sockpath.c_str(), strerror(errno));
-  }
-
-#if HAVE_EVENTFD
-  wakeup_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
-  if (wakeup_fd < 0) {
-    Fatal("unable to create wakeup eventfd. errno: %s", strerror(errno));
-  }
-#endif
-
-  data_len          = sizeof(pid_t);
-  mh_full           = static_cast<MgmtMessageHdr *>(alloca(sizeof(MgmtMessageHdr) + data_len));
-  mh_full->msg_id   = MGMT_SIGNAL_PID;
-  mh_full->data_len = data_len;
-
-  memcpy(reinterpret_cast<char *>(mh_full) + sizeof(MgmtMessageHdr), &(pid), data_len);
-
-  if (mgmt_write_pipe(local_manager_sockfd, reinterpret_cast<char *>(mh_full), sizeof(MgmtMessageHdr) + data_len) <= 0) {
-    Fatal("error writing message: %s", strerror(errno));
-  }
-}
-
-int
-ProcessManager::pollLMConnection()
-{
-  int count;
-  int ready;
-  struct timeval timeout;
-  fd_set fdlist;
-
-  // Avoid getting stuck enqueuing too many requests in a row, limit to MAX_MSGS_IN_A_ROW.
-  for (count = 0; running && count < max_msgs_in_a_row; ++count) {
-    timeout.tv_sec  = 1;
-    timeout.tv_usec = 0;
-
-    FD_ZERO(&fdlist);
-
-    if (local_manager_sockfd != ts::NO_FD) {
-      FD_SET(local_manager_sockfd, &fdlist);
-    }
-
-#if HAVE_EVENTFD
-    if (wakeup_fd != ts::NO_FD) {
-      FD_SET(wakeup_fd, &fdlist);
-    }
-#endif
-
-    // wait for data on socket
-    ready = mgmt_select(FD_SETSIZE, &fdlist, nullptr, nullptr, &timeout);
-
-    switch (ready) {
-    case 0:
-      // Timed out.
-      return 0;
-    case -1:
-      if (mgmt_transient_error()) {
-        continue;
-      }
-      return -errno;
-    }
-
-    if (local_manager_sockfd != ts::NO_FD && FD_ISSET(local_manager_sockfd, &fdlist)) { /* Message from manager */
-      MgmtMessageHdr *msg;
-
-      int ret = read_management_message(local_manager_sockfd, &msg);
-      if (ret < 0) {
-        return ret;
-      }
-
-      // No message, we are done polling. */
-      if (msg == nullptr) {
-        return 0;
-      }
-
-      Debug("pmgmt", "received message ID %d", msg->msg_id);
-      handleMgmtMsgFromLM(msg);
-    }
-#if HAVE_EVENTFD
-    else if (wakeup_fd != ts::NO_FD && FD_ISSET(wakeup_fd, &fdlist)) { /* if msg, keep polling for more */
-      // read or else fd will always be set.
-      uint64_t ignore;
-      ATS_UNUSED_RETURN(read(wakeup_fd, &ignore, sizeof(uint64_t)));
-      break;
-    }
-#endif
-  }
-  Debug("pmgmt", "enqueued %d of max %d messages in a row", count, max_msgs_in_a_row);
-  return 0;
-}
-
-void
-ProcessManager::handleMgmtMsgFromLM(MgmtMessageHdr *mh)
-{
-  ink_assert(mh != nullptr);
-
-  auto payload = mh->payload();
-
-  Debug("pmgmt", "processing event id '%d' payload=%d", mh->msg_id, mh->data_len);
-  switch (mh->msg_id) {
-  case MGMT_EVENT_SHUTDOWN:
-    executeMgmtCallback(MGMT_EVENT_SHUTDOWN, {});
-    Alert("exiting on shutdown message");
-    break;
-  case MGMT_EVENT_RESTART:
-    executeMgmtCallback(MGMT_EVENT_RESTART, {});
-    break;
-  case MGMT_EVENT_DRAIN:
-    executeMgmtCallback(MGMT_EVENT_DRAIN, payload);
-    break;
-  case MGMT_EVENT_CLEAR_STATS:
-    executeMgmtCallback(MGMT_EVENT_CLEAR_STATS, {});
-    break;
-  case MGMT_EVENT_HOST_STATUS_UP:
-    executeMgmtCallback(MGMT_EVENT_HOST_STATUS_UP, payload);
-    break;
-  case MGMT_EVENT_HOST_STATUS_DOWN:
-    executeMgmtCallback(MGMT_EVENT_HOST_STATUS_DOWN, payload);
-    break;
-  case MGMT_EVENT_ROLL_LOG_FILES:
-    executeMgmtCallback(MGMT_EVENT_ROLL_LOG_FILES, {});
-    break;
-  case MGMT_EVENT_PLUGIN_CONFIG_UPDATE: {
-    auto msg{payload.rebind<char>()};
-    if (!msg.empty() && msg[0] != '\0' && this->cbtable) {
-      this->cbtable->invoke(msg.data());
-    }
-  } break;
-  case MGMT_EVENT_CONFIG_FILE_UPDATE:
-    /*
-      librecords -- we don't do anything in here because we are traffic_server
-      and we are not the owner of proxy.config.* variables.
-      Even if we trigger the sync_required bit, by
-      RecSetSynRequired, the sync. message will send back to
-      traffic_manager. And traffic_manager founds out that, the
-      actual value of the config variable didn't changed.
-      At the end, the sync_required bit is not set and we will
-      never get notified and callbacks are never invoked.
-
-      The solution is to set the sync_required bit on the
-      manager side. See LocalManager::sendMgmtMsgToProcesses()
-      for details.
-    */
-    break;
-  case MGMT_EVENT_LIBRECORDS:
-    executeMgmtCallback(MGMT_EVENT_LIBRECORDS, payload);
-    break;
-  case MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE:
-    executeMgmtCallback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, payload);
-    break;
-  case MGMT_EVENT_LIFECYCLE_MESSAGE:
-    executeMgmtCallback(MGMT_EVENT_LIFECYCLE_MESSAGE, payload);
-    break;
-  default:
-    Warning("received unknown message ID %d\n", mh->msg_id);
-    break;
-  }
-
-  ats_free(mh);
-}
diff --git a/mgmt/ProcessManager.h b/mgmt/ProcessManager.h
deleted file mode 100644
index 926d229..0000000
--- a/mgmt/ProcessManager.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/** @file
-
-  Process Manager Class, derived from BaseManager. Class provides callback
-  registration for management events as well as the interface to the outside
-  world.
-
-  @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.
- */
-
-#pragma once
-
-#include <functional>
-#include <string_view>
-
-#include <ts/apidefs.h>
-
-#include "MgmtUtils.h"
-#include "BaseManager.h"
-#include "tscore/ink_sock.h"
-#include "tscore/ink_llqueue.h"
-#include "tscore/ink_apidefs.h"
-
-#if HAVE_EVENTFD
-#include <sys/eventfd.h>
-#endif
-
-class ConfigUpdateCbTable;
-
-class ProcessManager : public BaseManager
-{
-public:
-  ProcessManager(bool rlm);
-  ~ProcessManager();
-
-  // Start a thread for the process manager. If @a cb is set then it
-  // is called after the thread is started and before any messages are
-  // processed.
-  void start(std::function<TSThread()> const &cb_init        = std::function<TSThread()>(),
-             std::function<void(TSThread)> const &cb_destroy = std::function<void(TSThread)>());
-
-  // Stop the process manager, dropping any unprocessed messages.
-  void stop();
-
-  void signalConfigFileChild(const char *parent, const char *child);
-  void signalManager(int msg_id, const char *data_str);
-  void signalManager(int msg_id, const char *data_raw, int data_len);
-
-  /** Send a management message of type @a msg_id with @a text.
-   *
-   * @param msg_id ID for the message.
-   * @param text Content for the message.
-   *
-   * A terminating null character is added automatically.
-   */
-  void signalManager(int msg_id, std::string_view text);
-
-  void signalManager(MgmtMessageHdr *mh);
-
-  void reconfigure();
-  void initLMConnection();
-  void handleMgmtMsgFromLM(MgmtMessageHdr *mh);
-
-  void
-  registerPluginCallbacks(ConfigUpdateCbTable *_cbtable)
-  {
-    cbtable = _cbtable;
-  }
-
-private:
-  int pollLMConnection();
-  int processSignalQueue();
-  bool processEventQueue();
-
-  bool require_lm;
-  RecInt timeout;
-  LLQ *mgmt_signal_queue;
-  pid_t pid;
-
-  ink_thread poll_thread = ink_thread_null();
-  int running            = 0;
-
-  /// Thread initialization callback.
-  /// This allows @c traffic_server and @c traffic_manager to perform different initialization in the thread.
-  std::function<TSThread()> init;
-  std::function<void(TSThread)> destroy;
-  TSThread managerThread = nullptr;
-
-  int local_manager_sockfd;
-#if HAVE_EVENTFD
-  int wakeup_fd; // external trigger to stop polling
-#endif
-  ConfigUpdateCbTable *cbtable;
-  int max_msgs_in_a_row;
-
-  static const int MAX_MSGS_IN_A_ROW = 10000;
-  static void *processManagerThread(void *arg);
-};
-
-extern ProcessManager *pmgmt;
diff --git a/mgmt/ProxyConfig.h b/mgmt/ProxyConfig.h
index 493dec2..e55be71 100644
--- a/mgmt/ProxyConfig.h
+++ b/mgmt/ProxyConfig.h
@@ -25,23 +25,10 @@
 
 #include <atomic>
 
-#include "ProcessManager.h"
 #include "I_Tasks.h"
 
 class ProxyMutex;
 
-//
-// Macros that spin waiting for the data to be bound
-//
-#define SignalManager(_n, _d) pmgmt->signalManager(_n, (char *)_d)
-#define SignalWarning(_n, _s) \
-  {                           \
-    Warning("%s", _s);        \
-    SignalManager(_n, _s);    \
-  }
-
-#define RegisterMgmtCallback(_signal, _fn, _data) pmgmt->registerMgmtCallback(_signal, _fn, _data)
-
 #define MAX_CONFIGS 100
 
 typedef RefCountObj ConfigInfo;
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 9aa9af3..3bf7cf9 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -53,12 +53,6 @@
   ,
   {RECT_CONFIG, "proxy.config.bin_path", RECD_STRING, TS_BUILD_BINDIR, RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_READ_ONLY}
   ,
-  {RECT_CONFIG, "proxy.config.proxy_binary", RECD_STRING, "traffic_server", RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.manager_binary", RECD_STRING, "traffic_manager", RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.proxy_binary_opts", RECD_STRING, "-M", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
   {RECT_CONFIG, "proxy.config.env_prep", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   // Jira TS-21
@@ -248,24 +242,11 @@
 
   //##############################################################################
   //#
-  //# Local Manager
+  //# Management
   //#
   //##############################################################################
-  {RECT_CONFIG, "proxy.config.lm.pserver_timeout_secs", RECD_INT, "1", RECU_RESTART_TM, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.lm.pserver_timeout_msecs", RECD_INT, "0", RECU_RESTART_TM, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.admin.autoconf.localhost_only", RECD_INT, "1", RECU_RESTART_TM, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
-  ,
   {RECT_CONFIG, "proxy.config.admin.user_id", RECD_STRING, TS_PKGSYSUSER, RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_READ_ONLY}
   ,
-  {RECT_CONFIG, "proxy.config.admin.cli_path", RECD_STRING, "cli", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.admin.api.restricted", RECD_INT, "0", RECU_RESTART_TM, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.track_config_files", RECD_INT, "1", RECU_RESTART_TM, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
-  ,
-
   //##############################################################################
   //#
   //# UDP configuration stuff: hidden variables
@@ -279,13 +260,7 @@
   ,
   {RECT_CONFIG, "proxy.config.udp.threads", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-
-  //##############################################################################
-  //#
-  //# Process Manager
-  //#
-  //##############################################################################
-  {RECT_CONFIG, "proxy.config.process_manager.timeout", RECD_INT, "5", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+  {RECT_CONFIG, "proxy.config.udp.enable_gso", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
 
   //##############################################################################
@@ -406,10 +381,6 @@
         ,
   {RECT_CONFIG, "proxy.config.http.per_server.connection.alert_delay", RECD_INT, "60", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
         ,
-  {RECT_CONFIG, "proxy.config.http.per_server.connection.queue_size", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
-    ,
-  {RECT_CONFIG, "proxy.config.http.per_server.connection.queue_delay", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
-        ,
   {RECT_CONFIG, "proxy.config.http.per_server.connection.min", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http.attach_server_session_to_client", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
@@ -451,8 +422,6 @@
   ,
   {RECT_CONFIG, "proxy.config.http.parent_proxy.per_parent_connect_attempts", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http.parent_proxy.connect_attempts_timeout", RECD_INT, "30", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
   {RECT_CONFIG, "proxy.config.http.parent_proxy.mark_down_hostdb", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http.parent_proxy.self_detect", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
@@ -511,8 +480,6 @@
   ,
   {RECT_CONFIG, "proxy.config.http.connect_attempts_timeout", RECD_INT, "30", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http.post_connect_attempts_timeout", RECD_INT, "1800", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
   {RECT_CONFIG, "proxy.config.http.connect.dead.policy", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http.down_server.cache_time", RECD_INT, "60", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
@@ -1152,6 +1119,8 @@
   ,
   {RECT_CONFIG, "proxy.config.ssl.client.certification_level", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL}
   ,
+  {RECT_CONFIG, "proxy.config.ssl.client.alpn_protocols", RECD_STRING, nullptr, RECU_RESTART_TS, RR_NULL, RECC_STR, "^[^[:space:]]*$", RECA_NULL}
+  ,
   {RECT_CONFIG, "proxy.config.ssl.server.cert.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.ssl.server.cert_chain.filename", RECD_STRING, nullptr, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
@@ -1170,7 +1139,7 @@
   ,
   {RECT_CONFIG, "proxy.config.ssl.CA.cert.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.ssl.client.verify.server.policy", RECD_STRING, "PERMISSIVE", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+  {RECT_CONFIG, "proxy.config.ssl.client.verify.server.policy", RECD_STRING, "ENFORCED", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.ssl.client.verify.server.properties", RECD_STRING, "ALL", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
@@ -1281,10 +1250,6 @@
   {RECT_CONFIG, "proxy.config.plugin.load_elevated", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_READ_ONLY}
   ,
 
-  // Interim configuration setting for obeying keepalive requests on internal
-  // (PluginVC) sessions. See TS-4960 and friends.
-  {RECT_LOCAL, "proxy.config.http.keepalive_internal_vc", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL},
-
   //##############################################################################
   //#
   //# Local Manager Specific Records File
@@ -1302,8 +1267,6 @@
   //#
   //# Restart Stats
   //#
-  {RECT_NODE, "proxy.node.restarts.manager.start_time", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
   {RECT_NODE, "proxy.node.restarts.proxy.start_time", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_NODE, "proxy.node.restarts.proxy.cache_ready_time", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
@@ -1313,32 +1276,6 @@
   {RECT_NODE, "proxy.node.restarts.proxy.restart_count", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   //#
-  //# Manager Version Info
-  //#
-  {RECT_NODE, "proxy.node.version.manager.short", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_NODE, "proxy.node.version.manager.long", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_NODE, "proxy.node.version.manager.build_number", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_NODE, "proxy.node.version.manager.build_time", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_NODE, "proxy.node.version.manager.build_date", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_NODE, "proxy.node.version.manager.build_machine", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_NODE, "proxy.node.version.manager.build_person", RECD_STRING, nullptr, RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  //# manager: backoff configuration.
-  {RECT_CONFIG, "proxy.node.config.manager_exponential_sleep_ceiling", RECD_INT, "60", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.node.config.manager_retry_cap", RECD_INT, "5", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  //# manager: log filename
-  {RECT_CONFIG, "proxy.node.config.manager_log_filename", RECD_STRING, "manager.log", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[^[:space:]]+$", RECA_NULL}
-  ,
-
-  //#
   //# SSL parent proxying info
   //#
   //# Set the value of this variable to 1 if this node
@@ -1368,6 +1305,8 @@
   ,
   {RECT_CONFIG, "proxy.config.http2.initial_window_size_in", RECD_INT, "65535", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
+  {RECT_CONFIG, "proxy.config.http2.flow_control.policy_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "[0-2]", RECA_NULL}
+  ,
   {RECT_CONFIG, "proxy.config.http2.max_frame_size", RECD_INT, "16384", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http2.header_table_size", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
@@ -1565,7 +1504,10 @@
   //# 0 - no checking. 1 - log in mismatch. 2 - enforcing
   //#
   //###########
-  {RECT_CONFIG, "proxy.config.http.host_sni_policy", RECD_INT, "2", RECU_NULL, RR_NULL, RECC_NULL, "[0-2]", RECA_NULL},
+  {RECT_CONFIG, "proxy.config.http.host_sni_policy", RECD_INT, "2", RECU_NULL, RR_NULL, RECC_NULL, "[0-2]", RECA_NULL}
+  ,
+  {RECT_CONFIG, "proxy.config.jsonrpc.filename", RECD_STRING, ts::filename::JSONRPC, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+  ,
 
   //###########
   //#
diff --git a/mgmt/WebMgmtUtils.cc b/mgmt/WebMgmtUtils.cc
index 4815446..d616b26 100644
--- a/mgmt/WebMgmtUtils.cc
+++ b/mgmt/WebMgmtUtils.cc
@@ -28,76 +28,6 @@
 #include "WebMgmtUtils.h"
 #include "tscore/Regex.h"
 
-// bool varSetFromStr(const char*, const char* )
-//
-// Sets the named local manager variable from the value string
-// passed in.  Does the appropriate type conversion on
-// value string to get it to the type of the local manager
-// variable
-//
-//  returns true if the variable was successfully set
-//   and false otherwise
-//
-bool
-varSetFromStr(const char *varName, const char *value)
-{
-  RecDataT varDataType = RECD_NULL;
-  bool found           = true;
-  int err              = REC_ERR_FAIL;
-  RecData data;
-
-  memset(&data, 0, sizeof(RecData));
-
-  err = RecGetRecordDataType(const_cast<char *>(varName), &varDataType);
-  if (err != REC_ERR_OKAY) {
-    return found;
-  }
-  // Use any empty string if we get a NULL so
-  //  sprintf does puke.  However, we need to
-  //  switch this back to NULL for STRING types
-  if (value == nullptr) {
-    value = "";
-  }
-
-  switch (varDataType) {
-  case RECD_INT:
-    if (sscanf(value, "%" PRId64 "", &data.rec_int) == 1) {
-      RecSetRecordInt(const_cast<char *>(varName), data.rec_int, REC_SOURCE_EXPLICIT);
-    } else {
-      found = false;
-    }
-    break;
-  case RECD_COUNTER:
-    if (sscanf(value, "%" PRId64 "", &data.rec_counter) == 1) {
-      RecSetRecordCounter(const_cast<char *>(varName), data.rec_counter, REC_SOURCE_EXPLICIT);
-    } else {
-      found = false;
-    }
-    break;
-  case RECD_FLOAT:
-    // coverity[secure_coding]
-    if (sscanf(value, "%f", &data.rec_float) == 1) {
-      RecSetRecordFloat(const_cast<char *>(varName), data.rec_float, REC_SOURCE_EXPLICIT);
-    } else {
-      found = false;
-    }
-    break;
-  case RECD_STRING:
-    if (*value == '\0') {
-      RecSetRecordString(const_cast<char *>(varName), nullptr, REC_SOURCE_EXPLICIT);
-    } else {
-      RecSetRecordString(const_cast<char *>(varName), const_cast<char *>(value), REC_SOURCE_EXPLICIT);
-    }
-    break;
-  case RECD_NULL:
-  default:
-    found = false;
-    break;
-  }
-
-  return found;
-}
-
 // bool varSetFloat(const char* varName, RecFloat value)
 //
 //  Sets the variable specified by varName to value.  varName
@@ -292,154 +222,6 @@
   return (err == REC_ERR_OKAY);
 }
 
-// bool varCounterFromName (const char*, RecFloat* )
-//
-//   Sets the *value to value of the varName.
-//
-//  return true if bufVal was successfully set
-//    and false otherwise
-//
-bool
-varCounterFromName(const char *varName, RecCounter *value)
-{
-  RecDataT varDataType = RECD_NULL;
-  bool found           = true;
-  int err              = REC_ERR_FAIL;
-
-  err = RecGetRecordDataType(const_cast<char *>(varName), &varDataType);
-
-  if (err == REC_ERR_FAIL) {
-    return false;
-  }
-
-  switch (varDataType) {
-  case RECD_INT: {
-    RecInt tempInt = 0;
-    RecGetRecordInt(const_cast<char *>(varName), &tempInt);
-    *value = static_cast<RecCounter>(tempInt);
-    break;
-  }
-  case RECD_COUNTER: {
-    *value = 0;
-    RecGetRecordCounter(const_cast<char *>(varName), value);
-    break;
-  }
-  case RECD_FLOAT: {
-    RecFloat tempFloat = 0.0;
-    RecGetRecordFloat(const_cast<char *>(varName), &tempFloat);
-    *value = static_cast<RecCounter>(tempFloat);
-    break;
-  }
-  case RECD_STRING:
-  case RECD_NULL:
-  default:
-    *value = -1;
-    found  = false;
-    break;
-  }
-
-  return found;
-}
-
-// bool varFloatFromName (const char*, RecFloat* )
-//
-//   Sets the *value to value of the varName.
-//
-//  return true if bufVal was successfully set
-//    and false otherwise
-//
-bool
-varFloatFromName(const char *varName, RecFloat *value)
-{
-  RecDataT varDataType = RECD_NULL;
-  bool found           = true;
-
-  int err = REC_ERR_FAIL;
-
-  err = RecGetRecordDataType(const_cast<char *>(varName), &varDataType);
-
-  if (err == REC_ERR_FAIL) {
-    return false;
-  }
-
-  switch (varDataType) {
-  case RECD_INT: {
-    RecInt tempInt = 0;
-    RecGetRecordInt(const_cast<char *>(varName), &tempInt);
-    *value = static_cast<RecFloat>(tempInt);
-    break;
-  }
-  case RECD_COUNTER: {
-    RecCounter tempCounter = 0;
-    RecGetRecordCounter(const_cast<char *>(varName), &tempCounter);
-    *value = static_cast<RecFloat>(tempCounter);
-    break;
-  }
-  case RECD_FLOAT: {
-    *value = 0.0;
-    RecGetRecordFloat(const_cast<char *>(varName), value);
-    break;
-  }
-  case RECD_STRING:
-  case RECD_NULL:
-  default:
-    *value = -1.0;
-    found  = false;
-    break;
-  }
-
-  return found;
-}
-
-// bool varIntFromName (const char*, RecInt* )
-//
-//   Sets the *value to value of the varName.
-//
-//  return true if bufVal was successfully set
-//    and false otherwise
-//
-bool
-varIntFromName(const char *varName, RecInt *value)
-{
-  RecDataT varDataType = RECD_NULL;
-  bool found           = true;
-  int err              = REC_ERR_FAIL;
-
-  err = RecGetRecordDataType(const_cast<char *>(varName), &varDataType);
-
-  if (err != REC_ERR_OKAY) {
-    return false;
-  }
-
-  switch (varDataType) {
-  case RECD_INT: {
-    *value = 0;
-    RecGetRecordInt(const_cast<char *>(varName), value);
-    break;
-  }
-  case RECD_COUNTER: {
-    RecCounter tempCounter = 0;
-    RecGetRecordCounter(const_cast<char *>(varName), &tempCounter);
-    *value = static_cast<RecInt>(tempCounter);
-    break;
-  }
-  case RECD_FLOAT: {
-    RecFloat tempFloat = 0.0;
-    RecGetRecordFloat(const_cast<char *>(varName), &tempFloat);
-    *value = static_cast<RecInt>(tempFloat);
-    break;
-  }
-  case RECD_STRING:
-  case RECD_NULL:
-  default:
-    *value = -1;
-    found  = false;
-    break;
-  }
-
-  return found;
-}
-
 // void percentStrFromFloat(MgmtFloat, char* bufVal)
 //
 //  Converts a float to a percent string
@@ -529,252 +311,6 @@
   }
 }
 
-// bool varStrFromName (const char*, char*, int)
-//
-//   Sets the bufVal string to the value of the local manager
-//     named by varName.  bufLen is size of bufVal
-//
-//  return true if bufVal was successfully set
-//    and false otherwise
-//
-//  EVIL ALERT: overviewRecord::varStrFromName is extremely
-//    similar to this function except in how it gets it's
-//    data.  Changes to this function must be propagated
-//    to its twin.  Cut and Paste sucks but there is not
-//    an easy way to merge the functions
-//
-bool
-varStrFromName(const char *varNameConst, char *bufVal, int bufLen)
-{
-  char *varName        = nullptr;
-  RecDataT varDataType = RECD_NULL;
-  bool found           = true;
-  int varNameLen       = 0;
-  char formatOption    = '\0';
-  RecData data;
-  int err = REC_ERR_FAIL;
-
-  memset(&data, 0, sizeof(RecData));
-
-  // Check to see if there is a \ option on the end of variable
-  //   \ options indicate that we need special formatting
-  //   of the results.  Supported \ options are
-  //
-  ///  b - bytes.  Ints and Counts only.  Amounts are
-  //       transformed into one of GB, MB, KB, or B
-  //
-  varName    = ats_strdup(varNameConst);
-  varNameLen = strlen(varName);
-  if (varNameLen > 3 && varName[varNameLen - 2] == '\\') {
-    formatOption = varName[varNameLen - 1];
-
-    // Now that we know the format option, terminate the string
-    //   to make the option disappear
-    varName[varNameLen - 2] = '\0';
-
-    // Return not found for unknown format options
-    if (formatOption != 'b' && formatOption != 'm' && formatOption != 'c' && formatOption != 'p') {
-      ats_free(varName);
-      return false;
-    }
-  }
-
-  err = RecGetRecordDataType(varName, &varDataType);
-  if (err == REC_ERR_FAIL) {
-    ats_free(varName);
-    return false;
-  }
-
-  switch (varDataType) {
-  case RECD_INT:
-    RecGetRecordInt(varName, &data.rec_int);
-    if (formatOption == 'b') {
-      bytesFromInt(data.rec_int, bufVal, bufLen);
-    } else if (formatOption == 'm') {
-      MbytesFromInt(data.rec_int, bufVal, bufLen);
-    } else if (formatOption == 'c') {
-      commaStrFromInt(data.rec_int, bufVal, bufLen);
-    } else {
-      snprintf(bufVal, bufLen, "%" PRId64 "", data.rec_int);
-    }
-    break;
-
-  case RECD_COUNTER:
-    RecGetRecordCounter(varName, &data.rec_counter);
-    if (formatOption == 'b') {
-      bytesFromInt(static_cast<MgmtInt>(data.rec_counter), bufVal, bufLen);
-    } else if (formatOption == 'm') {
-      MbytesFromInt(static_cast<MgmtInt>(data.rec_counter), bufVal, bufLen);
-    } else if (formatOption == 'c') {
-      commaStrFromInt(data.rec_counter, bufVal, bufLen);
-    } else {
-      snprintf(bufVal, bufLen, "%" PRId64 "", data.rec_counter);
-    }
-    break;
-  case RECD_FLOAT:
-    RecGetRecordFloat(varName, &data.rec_float);
-    if (formatOption == 'p') {
-      percentStrFromFloat(data.rec_float, bufVal, bufLen);
-    } else {
-      snprintf(bufVal, bufLen, "%.2f", data.rec_float);
-    }
-    break;
-  case RECD_STRING:
-    RecGetRecordString_Xmalloc(varName, &data.rec_string);
-    if (data.rec_string == nullptr) {
-      bufVal[0] = '\0';
-    } else if (strlen(data.rec_string) < static_cast<size_t>(bufLen - 1)) {
-      ink_strlcpy(bufVal, data.rec_string, bufLen);
-    } else {
-      ink_strlcpy(bufVal, data.rec_string, bufLen);
-    }
-    ats_free(data.rec_string);
-    break;
-  default:
-    found = false;
-    break;
-  }
-
-  ats_free(varName);
-  return found;
-}
-
-// bool MgmtData::setFromName(const char*)
-//
-//    Fills in class variables from the given
-//      variable name
-//
-//    Returns true if the information could be set
-//     and false otherwise
-//
-bool
-MgmtData::setFromName(const char *varName)
-{
-  bool found = true;
-  int err;
-
-  err = RecGetRecordDataType(const_cast<char *>(varName), &this->type);
-
-  if (err == REC_ERR_FAIL) {
-    return found;
-  }
-
-  switch (this->type) {
-  case RECD_INT:
-    RecGetRecordInt(const_cast<char *>(varName), &this->data.rec_int);
-    break;
-  case RECD_COUNTER:
-    RecGetRecordCounter(const_cast<char *>(varName), &this->data.rec_counter);
-    break;
-  case RECD_FLOAT:
-    RecGetRecordFloat(const_cast<char *>(varName), &this->data.rec_float);
-    break;
-  case RECD_STRING:
-    RecGetRecordString_Xmalloc(const_cast<char *>(varName), &this->data.rec_string);
-    break;
-  case RECD_NULL:
-  default:
-    found = false;
-    break;
-  }
-
-  return found;
-}
-
-MgmtData::MgmtData()
-{
-  type = RECD_NULL;
-  memset(&data, 0, sizeof(RecData));
-}
-
-MgmtData::~MgmtData()
-{
-  if (type == RECD_STRING) {
-    ats_free(data.rec_string);
-  }
-}
-
-// MgmtData::compareFromString(const char* str, strLen)
-//
-//  Compares the value of string converted to
-//    data type of this_>type with value
-//    held in this->data
-//
-bool
-MgmtData::compareFromString(const char *str)
-{
-  RecData compData;
-  bool compare = false;
-  float floatDiff;
-
-  switch (this->type) {
-  case RECD_INT:
-    // TODO: Add SI decimal multipliers rule ?
-    if (str && recordRegexCheck("^[0-9]+$", str)) {
-      compData.rec_int = ink_atoi64(str);
-      if (data.rec_int == compData.rec_int) {
-        compare = true;
-      }
-    }
-    break;
-  case RECD_COUNTER:
-    if (str && recordRegexCheck("^[0-9]+$", str)) {
-      compData.rec_counter = ink_atoi64(str);
-      if (data.rec_counter == compData.rec_counter) {
-        compare = true;
-      }
-    }
-    break;
-  case RECD_FLOAT:
-    compData.rec_float = atof(str);
-    // HACK - There are some rounding problems with
-    //   floating point numbers so say we have a match if there difference
-    //   is small
-    floatDiff = data.rec_float - compData.rec_float;
-    if (floatDiff > -0.001 && floatDiff < 0.001) {
-      compare = true;
-    }
-    break;
-  case RECD_STRING:
-    if (str == nullptr || *str == '\0') {
-      if (data.rec_string == nullptr) {
-        compare = true;
-      }
-    } else {
-      if ((data.rec_string != nullptr) && (strcmp(str, data.rec_string) == 0)) {
-        compare = true;
-      }
-    }
-    break;
-  case RECD_NULL:
-  default:
-    compare = false;
-    break;
-  }
-
-  return compare;
-}
-
-// void RecDataT varType(const char* varName)
-//
-//   Simply return the variable type
-//
-RecDataT
-varType(const char *varName)
-{
-  RecDataT data_type;
-  int err;
-
-  err = RecGetRecordDataType(const_cast<char *>(varName), &data_type);
-
-  if (err == REC_ERR_FAIL) {
-    return RECD_NULL;
-  }
-
-  Debug("RecOp", "[varType] %s is of type %d", varName, data_type);
-  return data_type;
-}
-
 //
 // Removes any cr/lf line breaks from the text data
 //
@@ -885,209 +421,3 @@
   *safeCurrent = '\0';
   return safeBuf;
 }
-
-//
-//
-//  Sets the LocalManager variable:  proxy.node.hostname
-//
-//    To the fully qualified hostname for the machine
-//       that we are running on
-int
-setHostnameVar()
-{
-  char ourHostName[MAXDNAME];
-  char *firstDot;
-
-  // Get Our HostName
-  if (gethostname(ourHostName, MAXDNAME) < 0) {
-    mgmt_fatal(errno, "[setHostnameVar] Can not determine our hostname");
-  }
-
-  res_init();
-  appendDefaultDomain(ourHostName, MAXDNAME);
-
-  // FQ is a Fully Qualified hostname (ie: proxydev.example.com)
-  varSetFromStr("proxy.node.hostname_FQ", ourHostName);
-
-  // non-FQ is just the hostname (ie: proxydev)
-  firstDot = strchr(ourHostName, '.');
-  if (firstDot != nullptr) {
-    *firstDot = '\0';
-  }
-  varSetFromStr("proxy.node.hostname", ourHostName);
-
-  return 0;
-}
-
-// void appendDefaultDomain(char* hostname, int bufLength)
-//
-//   Appends the pasted in hostname with the default
-//     domain if the hostname is an unqualified name
-//
-//   The default domain is obtained from the resolver libraries
-//    data structure
-//
-//   Truncates the domain name if bufLength is too small
-//
-//
-void
-appendDefaultDomain(char *hostname, int bufLength)
-{
-  int len                 = strlen(hostname);
-  const char msg[]        = "Nodes will be know by their unqualified host name";
-  static int error_before = 0; // Race ok since effect is multiple error msg
-
-  ink_assert(len < bufLength);
-  ink_assert(bufLength >= 64);
-
-  // Ensure null termination of the result string
-  hostname[bufLength - 1] = '\0';
-
-  if (strchr(hostname, '.') == nullptr) {
-    if (_res.defdname[0] != '\0') {
-      if (bufLength - 2 >= static_cast<int>(strlen(hostname) + strlen(_res.defdname))) {
-        ink_strlcat(hostname, ".", bufLength);
-        ink_strlcat(hostname, _res.defdname, bufLength);
-      } else {
-        if (error_before == 0) {
-          mgmt_log("%s %s\n", "[appendDefaultDomain] Domain name is too long.", msg);
-          error_before++;
-        }
-      }
-    } else {
-      if (error_before == 0) {
-        mgmt_log("%s %s\n", "[appendDefaultDomain] Unable to determine default domain name.", msg);
-        error_before++;
-      }
-    }
-  }
-}
-
-bool
-recordValidityCheck(const char *varName, const char *value)
-{
-  RecCheckT check_t;
-  char *pattern;
-
-  if (RecGetRecordCheckType(const_cast<char *>(varName), &check_t) != REC_ERR_OKAY) {
-    return false;
-  }
-  if (RecGetRecordCheckExpr(const_cast<char *>(varName), &pattern) != REC_ERR_OKAY) {
-    return false;
-  }
-
-  switch (check_t) {
-  case RECC_STR:
-    if (recordRegexCheck(pattern, value)) {
-      return true;
-    }
-    break;
-  case RECC_INT:
-    if (recordRangeCheck(pattern, value)) {
-      return true;
-    }
-    break;
-  case RECC_IP:
-    if (recordIPCheck(pattern, value)) {
-      return true;
-    }
-    break;
-  case RECC_NULL:
-    // skip checking
-    return true;
-  default:
-    // unknown RecordCheckType...
-    mgmt_log("[WebMgmtUtil] error, unknown RecordCheckType for record %s\n", varName);
-  }
-
-  return false;
-}
-
-bool
-recordRegexCheck(const char *pattern, const char *value)
-{
-  pcre *regex;
-  const char *error;
-  int erroffset;
-
-  regex = pcre_compile(pattern, 0, &error, &erroffset, nullptr);
-  if (!regex) {
-    return false;
-  } else {
-    int r = pcre_exec(regex, nullptr, value, strlen(value), 0, 0, nullptr, 0);
-
-    pcre_free(regex);
-    return (r != -1) ? true : false;
-  }
-
-  return false; // no-op
-}
-
-bool
-recordRangeCheck(const char *pattern, const char *value)
-{
-  char *p = const_cast<char *>(pattern);
-  Tokenizer dashTok("-");
-
-  if (recordRegexCheck("^[0-9]+$", value)) {
-    while (*p != '[') {
-      p++;
-    } // skip to '['
-    if (dashTok.Initialize(++p, COPY_TOKS) == 2) {
-      int l_limit = atoi(dashTok[0]);
-      int u_limit = atoi(dashTok[1]);
-      int val     = atoi(value);
-      if (val >= l_limit && val <= u_limit) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-bool
-recordIPCheck(const char *pattern, const char *value)
-{
-  //  regex_t regex;
-  //  int result;
-  bool check;
-  const char *range_pattern = R"(\[[0-9]+\-[0-9]+\]\\\.\[[0-9]+\-[0-9]+\]\\\.\[[0-9]+\-[0-9]+\]\\\.\[[0-9]+\-[0-9]+\])";
-  const char *ip_pattern    = "[0-9]*[0-9]*[0-9].[0-9]*[0-9]*[0-9].[0-9]*[0-9]*[0-9].[0-9]*[0-9]*[0-9]";
-
-  Tokenizer dotTok1(".");
-  Tokenizer dotTok2(".");
-
-  check = true;
-  if (recordRegexCheck(range_pattern, pattern) && recordRegexCheck(ip_pattern, value)) {
-    if (dotTok1.Initialize(const_cast<char *>(pattern), COPY_TOKS) == 4 &&
-        dotTok2.Initialize(const_cast<char *>(value), COPY_TOKS) == 4) {
-      for (int i = 0; i < 4 && check; i++) {
-        if (!recordRangeCheck(dotTok1[i], dotTok2[i])) {
-          check = false;
-        }
-      }
-      if (check) {
-        return true;
-      }
-    }
-  } else if (strcmp(value, "") == 0) {
-    return true;
-  }
-  return false;
-}
-
-bool
-recordRestartCheck(const char *varName)
-{
-  RecUpdateT update_t;
-
-  if (RecGetRecordUpdateType(const_cast<char *>(varName), &update_t) != REC_ERR_OKAY) {
-    return false;
-  }
-
-  if (update_t == RECU_RESTART_TS || update_t == RECU_RESTART_TM) {
-    return true;
-  }
-
-  return false;
-}
diff --git a/mgmt/WebMgmtUtils.h b/mgmt/WebMgmtUtils.h
index 6522cf1..d7f4070 100644
--- a/mgmt/WebMgmtUtils.h
+++ b/mgmt/WebMgmtUtils.h
@@ -26,20 +26,6 @@
 #include "MgmtDefs.h"
 #include "records/P_RecCore.h"
 
-// class MgmtData - stores information from local manager
-//    variables in its native type
-//
-class MgmtData
-{
-public:
-  MgmtData();
-  ~MgmtData();
-  bool compareFromString(const char *str);
-  bool setFromName(const char *varName);
-  RecDataT type;
-  RecData data;
-};
-
 // Convert to byte units (GB, MB, KB)
 void bytesFromInt(RecInt bytes, char *bufVal, int bufLen);
 
@@ -52,14 +38,7 @@
 // Create percent string from float
 void percentStrFromFloat(RecFloat val, char *bufVal, int bufLen);
 
-// All types converted to/from strings where appropriate
-bool varStrFromName(const char *varName, char *bufVal, int bufLen);
-bool varSetFromStr(const char *varName, const char *value);
-
 // Converts where applicable to specified type
-bool varIntFromName(const char *varName, RecInt *value);
-bool varFloatFromName(const char *varName, RecFloat *value);
-bool varCounterFromName(const char *varName, RecCounter *value);
 bool varDataFromName(RecDataT varType, const char *varName, RecData *value);
 
 // No conversion done.  varName must represent a value of the appropriate
@@ -70,18 +49,6 @@
 bool varSetFloat(const char *varName, RecFloat value, bool convert = false);
 bool varSetData(RecDataT varType, const char *varName, RecData value);
 
-// Return the type of the variable named
-RecDataT varType(const char *varName);
-
 int convertHtmlToUnix(char *buffer);
 int substituteUnsafeChars(char *buffer);
 char *substituteForHTMLChars(const char *buffer);
-
-int setHostnameVar();
-void appendDefaultDomain(char *hostname, int bufLength);
-
-bool recordValidityCheck(const char *varName, const char *value);
-bool recordRegexCheck(const char *pattern, const char *value);
-bool recordRangeCheck(const char *pattern, const char *value);
-bool recordIPCheck(const char *pattern, const char *value);
-bool recordRestartCheck(const char *varName);
diff --git a/mgmt/api/APITestCliRemote.cc b/mgmt/api/APITestCliRemote.cc
deleted file mode 100644
index 8778b20..0000000
--- a/mgmt/api/APITestCliRemote.cc
+++ /dev/null
@@ -1,1026 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: APITestCliRemote.cc
- * Purpose: An interactive cli to test remote mgmt API; UNIT TEST for mgmtAPI
- * Created: lant
- *
- ***************************************************************************/
-
-/***************************************************************************
- * Possible Commands:
- ***************************************************************************
- * Control Operations:
- * -------------------
- * state:   returns ON (proxy is on) or OFF (proxy is off)
- * start:<tsArgs>  -   turns Proxy on, the tsArgs is optional;
- *                     it can either be  "hostdb" or "all",
- *                 eg. start, start:hostdb, start:all
- * stop:    turns Proxy off
- * restart: restarts Traffic Manager (Traffic Cop must be running)
- *
- * File operations:
- * ---------------
- * read_file:  reads hosting.config file
- * proxy.config.xxx (a records.config variable): returns value of that record
- * records: tests get/set/get a record of each different type
- *          (int, float, counter, string)
- * err_recs: stress test record get/set functions by purposely entering
- *              invalid record names and/or values
- * get_mlt: tests TSRecordGetMlt
- * set_mlt: tests TSRecordSetMlt
- *
- * read_url: tests TSReadFromUrl works by retrieving two valid urls
- * test_url: tests robustness of TSReadFromUrl using invalid urls
- *
- * Event Operations:
- * ----------------
- * active_events: lists the names of all currently active events
- * MGMT_ALARM_xxx (event_name specified in CoreAPIShared.h or Alarms.h):
- *                 resolves the specified event
- * register: registers a generic callback (=eventCallbackFn) which
- *           prints out the event name whenever an event is signalled
- * unregister: unregisters the generic callback function eventCallbackFn
- *
- * Diags
- * ----
- * diags - uses STATUS, NOTE, FATAL, ERROR diags
- *
- * Statistics
- * ----------
- * set_stats - sets dummy values for selected group of NODE, PROCESS
- *             records
- * print_stats - prints the values for the same selected group of records
- * reset_stats - resets all statistics to default values
- */
-
-#include "tscore/ink_config.h"
-#include "tscore/ink_defs.h"
-#include "tscore/ink_memory.h"
-#include <cstdlib>
-#include <cstring>
-#include <cstdio>
-#include <strings.h>
-#include "tscore/ink_string.h"
-
-#include "mgmtapi.h"
-
-// refer to test_records() function
-#define TEST_STRING 1
-#define TEST_FLOAT 1
-#define TEST_INT 1
-#define TEST_COUNTER 1
-#define TEST_REC_SET 1
-#define TEST_REC_GET 0
-#define TEST_REC_GET_2 0
-
-#define SET_INT 0
-
-/***************************************************************************
- * Printing Helper Functions
- ***************************************************************************/
-
-/* ------------------------------------------------------------------------
- * print_err
- * ------------------------------------------------------------------------
- * used to print the error description associated with the TSMgmtError err
- */
-void
-print_err(const char *module, TSMgmtError err)
-{
-  char *err_msg;
-
-  err_msg = TSGetErrorMessage(err);
-  printf("(%s) ERROR: %s\n", module, err_msg);
-
-  if (err_msg) {
-    TSfree(err_msg);
-  }
-}
-
-/*-------------------------------------------------------------
- * print_string_list
- *-------------------------------------------------------------*/
-void
-print_string_list(TSStringList list)
-{
-  int i, count, buf_pos = 0;
-  char buf[1000];
-
-  if (!list) {
-    return;
-  }
-  count = TSStringListLen(list);
-  for (i = 0; i < count; i++) {
-    char *str = TSStringListDequeue(list);
-    snprintf(buf + buf_pos, sizeof(buf) - buf_pos, "%s,", str);
-    buf_pos = strlen(buf);
-    TSStringListEnqueue(list, str);
-  }
-  printf("%s \n", buf);
-}
-
-/*-------------------------------------------------------------
- * print_int_list
- *-------------------------------------------------------------*/
-void
-print_int_list(TSIntList list)
-{
-  int i, count, buf_pos = 0;
-  char buf[1000];
-
-  count = TSIntListLen(list);
-  for (i = 0; i < count; i++) {
-    int *elem = TSIntListDequeue(list);
-    snprintf(buf + buf_pos, sizeof(buf) - buf_pos, "%d:", *elem);
-    buf_pos = strlen(buf);
-    TSIntListEnqueue(list, elem);
-  }
-  printf("Int List: %s \n", buf);
-}
-
-/***************************************************************************
- * Control Testing
- ***************************************************************************/
-void
-print_proxy_state()
-{
-  TSProxyStateT state = TSProxyStateGet();
-
-  switch (state) {
-  case TS_PROXY_ON:
-    printf("Proxy State = ON\n");
-    break;
-  case TS_PROXY_OFF:
-    printf("Proxy State = OFF\n");
-    break;
-  default:
-    printf("ERROR: Proxy State Undefined!\n");
-    break;
-  }
-}
-
-// starts Traffic Server (turns proxy on)
-void
-start_TS(char *tsArgs)
-{
-  TSMgmtError ret;
-  TSCacheClearT clear = TS_CACHE_CLEAR_NONE;
-  char *args;
-
-  strtok(tsArgs, ":");
-  args = strtok(nullptr, ":");
-  if (args) {
-    if (strcmp(args, "all\n") == 0) {
-      clear = TS_CACHE_CLEAR_CACHE;
-    } else if (strcmp(args, "hostdb\n") == 0) {
-      clear = TS_CACHE_CLEAR_HOSTDB;
-    }
-  } else {
-    clear = TS_CACHE_CLEAR_NONE;
-  }
-
-  printf("STARTING PROXY with cache: %d\n", clear);
-  if ((ret = TSProxyStateSet(TS_PROXY_ON, clear)) != TS_ERR_OKAY) {
-    printf("[TSProxyStateSet] turn on FAILED\n");
-  }
-  print_err("start_TS", ret);
-}
-
-// stops Traffic Server (turns proxy off)
-void
-stop_TS()
-{
-  TSMgmtError ret;
-
-  printf("STOPPING PROXY\n");
-  if ((ret = TSProxyStateSet(TS_PROXY_OFF, TS_CACHE_CLEAR_NONE)) != TS_ERR_OKAY) {
-    printf("[TSProxyStateSet] turn off FAILED\n");
-  }
-  print_err("stop_TS", ret);
-}
-
-// restarts Traffic Manager (Traffic Cop must be running)
-void
-restart()
-{
-  TSMgmtError ret;
-
-  printf("RESTART\n");
-  if ((ret = TSRestart(true)) != TS_ERR_OKAY) {
-    printf("[TSRestart] FAILED\n");
-  }
-
-  print_err("restart", ret);
-}
-
-// rereads all the configuration files
-void
-reconfigure()
-{
-  TSMgmtError ret;
-
-  printf("RECONFIGURE\n");
-  if ((ret = TSReconfigure()) != TS_ERR_OKAY) {
-    printf("[TSReconfigure] FAILED\n");
-  }
-
-  print_err("reconfigure", ret);
-}
-
-/* ------------------------------------------------------------------------
- * test_action_need
- * ------------------------------------------------------------------------
- * tests if correct action need is returned when requested record is set
- */
-void
-test_action_need()
-{
-  TSActionNeedT action;
-
-  // RU_NULL record
-  TSRecordSetString("proxy.config.proxy_name", "proxy_dorky", &action);
-  printf("[TSRecordSetString] proxy.config.proxy_name \n\tAction Should: [%d]\n\tAction is    : [%d]\n", TS_ACTION_UNDEFINED,
-         action);
-}
-
-/* Bouncer the traffic_server process(es) */
-void
-bounce()
-{
-  TSMgmtError ret;
-
-  printf("BOUNCER\n");
-  if ((ret = TSBounce(true)) != TS_ERR_OKAY) {
-    printf("[TSBounce] FAILED\n");
-  }
-
-  print_err("bounce", ret);
-}
-
-/***************************************************************************
- * Record Testing
- ***************************************************************************/
-
-/* ------------------------------------------------------------------------
- * test_error_records
- * ------------------------------------------------------------------------
- * stress test error handling by purposely being dumb; send requests to
- * get invalid record names
- */
-void
-test_error_records()
-{
-  TSInt port1, new_port = 8080;
-  TSActionNeedT action;
-  TSMgmtError ret;
-  TSCounter ctr1;
-
-  printf("\n");
-  // test get integer
-  fprintf(stderr, "Test invalid record names\n");
-
-  ret = TSRecordGetInt("proy.config.cop.core_signal", &port1);
-  if (ret != TS_ERR_OKAY) {
-    print_err("TSRecordGetInt", ret);
-  } else {
-    printf("[TSRecordGetInt] proxy.config.cop.core_signal=%" PRId64 " \n", port1);
-  }
-
-  // test set integer
-  ret = TSRecordSetInt("proy.config.cop.core_signal", new_port, &action);
-  print_err("TSRecordSetInt", ret);
-
-  printf("\n");
-  if (TSRecordGetCounter("proxy.press.socks.connections_successful", &ctr1) != TS_ERR_OKAY) {
-    printf("TSRecordGetCounter FAILED!\n");
-  } else {
-    printf("[TSRecordGetCounter]proxy.process.socks.connections_successful=%" PRId64 " \n", ctr1);
-  }
-}
-
-/* ------------------------------------------------------------------------
- * test_records
- * ------------------------------------------------------------------------
- * stress test record functionality by getting and setting different
- * records types; use the #defines defined above to determine which
- * type of tests you'd like turned on/off
- */
-void
-test_records()
-{
-  TSActionNeedT action;
-  char *rec_value;
-  char new_str[] = "new_record_value";
-  TSInt port1, port2, new_port  = 52432;
-  TSCounter ctr1, ctr2, new_ctr = 6666;
-  TSMgmtError err;
-
-  /********************* START TEST SECTION *****************/
-  printf("\n\n");
-
-#if SET_INT
-  // test set integer
-  if (TSRecordSetInt("proxy.config.cop.core_signal", new_port, &action) != TS_ERR_OKAY)
-    printf("TSRecordSetInt FAILED!\n");
-  else
-    printf("[TSRecordSetInt] proxy.config.cop.core_signal=%" PRId64 " \n", new_port);
-#endif
-
-#if TEST_REC_GET
-  TSRecordEle *rec_ele;
-  // retrieve a string value record using generic RecordGet
-  rec_ele = TSRecordEleCreate();
-  if (TSRecordGet("proxy.config.http.cache.vary_default_other", rec_ele) != TS_ERR_OKAY)
-    printf("TSRecordGet FAILED!\n");
-  else
-    printf("[TSRecordGet] proxy.config.http.cache.vary_default_other=%s\n", rec_ele->string_val);
-
-  TSRecordEleDestroy(rec_ele);
-  printf("\n\n");
-#endif
-
-#if TEST_REC_GET_2
-  // retrieve a string value record using generic RecordGet
-  rec_ele = TSRecordEleCreate();
-  if (TSRecordGet("proxy.config.proxy_name", rec_ele) != TS_ERR_OKAY)
-    printf("TSRecordGet FAILED!\n");
-  else
-    printf("[TSRecordGet] proxy.config.proxy_name=%s\n", rec_ele->string_val);
-
-  TSRecordEleDestroy(rec_ele);
-  printf("\n\n");
-#endif
-
-#if TEST_STRING
-  // retrieve an string value record using GetString
-  err = TSRecordGetString("proxy.config.proxy_name", &rec_value);
-  if (err != TS_ERR_OKAY) {
-    print_err("TSRecordGetString", err);
-  } else {
-    printf("[TSRecordGetString] proxy.config.proxy_name=%s\n", rec_value);
-  }
-  TSfree(rec_value);
-  rec_value = nullptr;
-
-  // test RecordSet
-  err = TSRecordSetString("proxy.config.proxy_name", (TSString)new_str, &action);
-  if (err != TS_ERR_OKAY) {
-    print_err("TSRecordSetString", err);
-  } else {
-    printf("[TSRecordSetString] proxy.config.proxy_name=%s\n", new_str);
-  }
-
-  // get
-  err = TSRecordGetString("proxy.config.proxy_name", &rec_value);
-  if (err != TS_ERR_OKAY) {
-    print_err("TSRecordGetString", err);
-  } else {
-    printf("[TSRecordGetString] proxy.config.proxy_name=%s\n", rec_value);
-  }
-  printf("\n");
-  TSfree(rec_value);
-#endif
-
-#if TEST_INT
-  printf("\n");
-  // test get integer
-  if (TSRecordGetInt("proxy.config.cop.core_signal", &port1) != TS_ERR_OKAY) {
-    printf("TSRecordGetInt FAILED!\n");
-  } else {
-    printf("[TSRecordGetInt] proxy.config.cop.core_signal=%" PRId64 " \n", port1);
-  }
-
-  // test set integer
-  if (TSRecordSetInt("proxy.config.cop.core_signal", new_port, &action) != TS_ERR_OKAY) {
-    printf("TSRecordSetInt FAILED!\n");
-  } else {
-    printf("[TSRecordSetInt] proxy.config.cop.core_signal=%" PRId64 " \n", new_port);
-  }
-
-  if (TSRecordGetInt("proxy.config.cop.core_signal", &port2) != TS_ERR_OKAY) {
-    printf("TSRecordGetInt FAILED!\n");
-  } else {
-    printf("[TSRecordGetInt] proxy.config.cop.core_signal=%" PRId64 " \n", port2);
-  }
-  printf("\n");
-#endif
-
-#if TEST_COUNTER
-  printf("\n");
-
-  if (TSRecordGetCounter("proxy.process.socks.connections_successful", &ctr1) != TS_ERR_OKAY) {
-    printf("TSRecordGetCounter FAILED!\n");
-  } else {
-    printf("[TSRecordGetCounter]proxy.process.socks.connections_successful=%" PRId64 " \n", ctr1);
-  }
-
-  if (TSRecordSetCounter("proxy.process.socks.connections_successful", new_ctr, &action) != TS_ERR_OKAY) {
-    printf("TSRecordSetCounter FAILED!\n");
-  } else {
-    printf("[TSRecordSetCounter] proxy.process.socks.connections_successful=%" PRId64 " \n", new_ctr);
-  }
-
-  if (TSRecordGetCounter("proxy.process.socks.connections_successful", &ctr2) != TS_ERR_OKAY) {
-    printf("TSRecordGetCounter FAILED!\n");
-  } else {
-    printf("[TSRecordGetCounter]proxy.process.socks.connections_successful=%" PRId64 " \n", ctr2);
-  }
-  printf("\n");
-#endif
-}
-
-// retrieves the value of the "proxy.config.xxx" record requested at input
-void
-test_rec_get(char *rec_name)
-{
-  TSRecordEle *rec_ele;
-  TSMgmtError ret;
-  char *name;
-
-  name = ats_strdup(rec_name);
-  printf("[test_rec_get] Get Record: %s\n", name);
-
-  // retrieve a string value record using generic RecordGet
-  rec_ele = TSRecordEleCreate();
-  if ((ret = TSRecordGet(name, rec_ele)) != TS_ERR_OKAY) {
-    printf("TSRecordGet FAILED!\n");
-  } else {
-    switch (rec_ele->rec_type) {
-    case TS_REC_INT:
-      printf("[TSRecordGet] %s=%" PRId64 "\n", name, rec_ele->valueT.int_val);
-      break;
-    case TS_REC_COUNTER:
-      printf("[TSRecordGet] %s=%" PRId64 "\n", name, rec_ele->valueT.counter_val);
-      break;
-    case TS_REC_FLOAT:
-      printf("[TSRecordGet] %s=%f\n", name, rec_ele->valueT.float_val);
-      break;
-    case TS_REC_STRING:
-      printf("[TSRecordGet] %s=%s\n", name, rec_ele->valueT.string_val);
-      break;
-    default:
-      // Handled here:
-      // TS_REC_UNDEFINED
-      break;
-    }
-  }
-
-  print_err("TSRecordGet", ret);
-
-  TSRecordEleDestroy(rec_ele);
-  TSfree(name);
-}
-
-/* ------------------------------------------------------------------------
- * test_record_get_mlt
- * ------------------------------------------------------------------------
- * Creates a list of record names to retrieve, and then batch request to
- * get list of records
- */
-void
-test_record_get_mlt()
-{
-  TSStringList name_list;
-  TSList rec_list;
-  int i, num;
-  char *v1, *v2, *v3, *v6, *v7;
-  TSMgmtError ret;
-
-  name_list = TSStringListCreate();
-  rec_list  = TSListCreate();
-
-  const size_t v1_size = (sizeof(char) * (strlen("proxy.config.proxy_name") + 1));
-  v1                   = static_cast<char *>(TSmalloc(v1_size));
-  ink_strlcpy(v1, "proxy.config.proxy_name", v1_size);
-  const size_t v2_size = (sizeof(char) * (strlen("proxy.config.bin_path") + 1));
-  v2                   = static_cast<char *>(TSmalloc(v2_size));
-  ink_strlcpy(v2, "proxy.config.bin_path", v2_size);
-  const size_t v3_size = (sizeof(char) * (strlen("proxy.config.manager_binary") + 1));
-  v3                   = static_cast<char *>(TSmalloc(v3_size));
-  ink_strlcpy(v3, "proxy.config.manager_binary", v3_size);
-  const size_t v6_size = (sizeof(char) * (strlen("proxy.config.env_prep") + 1));
-  v6                   = static_cast<char *>(TSmalloc(v6_size));
-  ink_strlcpy(v6, "proxy.config.env_prep", v6_size);
-  const size_t v7_size = (sizeof(char) * (strlen("proxy.config.cop.core_signal") + 1));
-  v7                   = static_cast<char *>(TSmalloc(v7_size));
-  ink_strlcpy(v7, "proxy.config.cop.core_signal", v7_size);
-
-  // add the names to the get_list
-  TSStringListEnqueue(name_list, v1);
-  TSStringListEnqueue(name_list, v2);
-  TSStringListEnqueue(name_list, v3);
-  TSStringListEnqueue(name_list, v6);
-  TSStringListEnqueue(name_list, v7);
-
-  num = TSStringListLen(name_list);
-  printf("Num Records to Get: %d\n", num);
-  ret = TSRecordGetMlt(name_list, rec_list);
-  // free the string list
-  TSStringListDestroy(name_list);
-  if (ret != TS_ERR_OKAY) {
-    print_err("TSStringListDestroy", ret);
-  }
-
-  for (i = 0; i < num; i++) {
-    TSRecordEle *rec_ele = static_cast<TSRecordEle *>(TSListDequeue(rec_list));
-    if (!rec_ele) {
-      printf("ERROR\n");
-      break;
-    }
-    printf("Record: %s = ", rec_ele->rec_name);
-    switch (rec_ele->rec_type) {
-    case TS_REC_INT:
-      printf("%" PRId64 "\n", rec_ele->valueT.int_val);
-      break;
-    case TS_REC_COUNTER:
-      printf("%" PRId64 "\n", rec_ele->valueT.counter_val);
-      break;
-    case TS_REC_FLOAT:
-      printf("%f\n", rec_ele->valueT.float_val);
-      break;
-    case TS_REC_STRING:
-      printf("%s\n", rec_ele->valueT.string_val);
-      break;
-    default:
-      // Handled here:
-      // TS_REC_UNDEFINED
-      break;
-    }
-    TSRecordEleDestroy(rec_ele);
-  }
-
-  TSListDestroy(rec_list); // must dequeue and free each string individually
-
-  return;
-}
-
-/* ------------------------------------------------------------------------
- * test_record_set_mlt
- * ------------------------------------------------------------------------
- * Creates a list of TSRecordEle's, and then batch request to set records
- * Also checks to make sure correct action_need type is set.
- */
-void
-test_record_set_mlt()
-{
-  TSList list;
-  TSRecordEle *ele1, *ele2;
-  TSActionNeedT action = TS_ACTION_UNDEFINED;
-  TSMgmtError err;
-
-  list = TSListCreate();
-
-  ele1                    = TSRecordEleCreate(); // TS_TYPE_UNDEFINED action
-  ele1->rec_name          = TSstrdup("proxy.config.cli_binary");
-  ele1->rec_type          = TS_REC_STRING;
-  ele1->valueT.string_val = TSstrdup(ele1->rec_name);
-
-  ele2                 = TSRecordEleCreate(); // undefined action
-  ele2->rec_name       = TSstrdup("proxy.config.cop.core_signal");
-  ele2->rec_type       = TS_REC_INT;
-  ele2->valueT.int_val = -4;
-
-  TSListEnqueue(list, ele1);
-  TSListEnqueue(list, ele2);
-
-  err = TSRecordSetMlt(list, &action);
-  print_err("TSRecordSetMlt", err);
-  fprintf(stderr, "[TSRecordSetMlt] Action Required: %d\n", action);
-
-  // cleanup: need to iterate through list and delete each ele
-  int count = TSListLen(list);
-  for (int i = 0; i < count; i++) {
-    TSRecordEle *ele = static_cast<TSRecordEle *>(TSListDequeue(list));
-    TSRecordEleDestroy(ele);
-  }
-  TSListDestroy(list);
-}
-
-/***************************************************************************
- * File I/O Testing
- ***************************************************************************/
-
-// if valid==true, then use a valid url to read
-void
-test_read_url(bool valid)
-{
-  char *header = nullptr;
-  int headerSize;
-  char *body = nullptr;
-  int bodySize;
-  TSMgmtError err;
-
-  if (!valid) {
-    // first try
-
-    err = TSReadFromUrlEx("hsdfasdf.com:80/index.html", &header, &headerSize, &body, &bodySize, 50000);
-    if (err != TS_ERR_OKAY) {
-      print_err("TSReadFromUrlEx", err);
-    } else {
-      printf("--------------------------------------------------------------\n");
-      //  printf("The header...\n%s\n%d\n", *header, *headerSize);
-      printf("--------------------------------------------------------------\n");
-      printf("The body...\n%s\n%d\n", body, bodySize);
-    }
-    if (body) {
-      TSfree(body);
-    }
-    if (header) {
-      TSfree(header);
-    }
-
-    err = TSReadFromUrlEx("http://sadfasdfi.com:80/", &header, &headerSize, &body, &bodySize, 50000);
-    if (err != TS_ERR_OKAY) {
-      print_err("TSReadFromUrlEx", err);
-    } else {
-      printf("---------------------------------------------------------------\n");
-      printf("The header...\n%s\n%d\n", header, headerSize);
-      printf("-------------------------------------------------------------\n");
-      printf("The body...\n%s\n%d\n", body, bodySize);
-    }
-    if (header) {
-      TSfree(header);
-    }
-    if (body) {
-      TSfree(body);
-    }
-
-  } else { // use valid urls
-    err = TSReadFromUrlEx("lakota.example.com:80/", &header, &headerSize, &body, &bodySize, 50000);
-
-    if (err != TS_ERR_OKAY) {
-      print_err("TSReadFromUrlEx", err);
-    } else {
-      printf("---------------------------------------------------------------\n");
-      printf("The header...\n%s\n%d\n", header, headerSize);
-      printf("-------------------------------------------------------------\n");
-      printf("The body...\n%s\n%d\n", body, bodySize);
-    }
-    if (header) {
-      TSfree(header);
-    }
-    if (body) {
-      TSfree(body);
-    }
-
-    // read second url
-    err = TSReadFromUrlEx("http://www.apache.org:80/index.html", &header, &headerSize, &body, &bodySize, 50000);
-    if (err != TS_ERR_OKAY) {
-      print_err("TSReadFromUrlEx", err);
-    } else {
-      printf("---------------------------------------------------------------\n");
-      printf("The header...\n%s\n%d\n", header, headerSize);
-      printf("-------------------------------------------------------------\n");
-      printf("The body...\n%s\n%d\n", body, bodySize);
-    }
-    if (header) {
-      TSfree(header);
-    }
-    if (body) {
-      TSfree(body);
-    }
-  }
-}
-
-/***************************************************************************
- * Events Testing
- ***************************************************************************/
-/* ------------------------------------------------------------------------
- * print_active_events
- * ------------------------------------------------------------------------
- * retrieves a list of all active events and prints out each event name,
- * one event per line
- */
-void
-print_active_events()
-{
-  TSList events;
-  TSMgmtError ret;
-  int count, i;
-  char *name;
-
-  printf("[print_active_events]\n");
-
-  events = TSListCreate();
-  ret    = TSActiveEventGetMlt(events);
-  if (ret != TS_ERR_OKAY) {
-    print_err("TSActiveEventGetMlt", ret);
-    goto END;
-  } else { // successful get
-    count = TSListLen(events);
-    for (i = 0; i < count; i++) {
-      name = static_cast<char *>(TSListDequeue(events));
-      printf("\t%s\n", name);
-      TSfree(name);
-    }
-  }
-
-END:
-  TSListDestroy(events);
-  return;
-}
-
-/* ------------------------------------------------------------------------
- * check_active
- * ------------------------------------------------------------------------
- * returns true if the event named event_name is currently active (unresolved)
- * returns false otherwise
- */
-bool
-check_active(char *event_name)
-{
-  bool active;
-  TSMgmtError ret;
-
-  ret = TSEventIsActive(event_name, &active);
-  print_err("TSEventIsActive", ret);
-
-  if (active) {
-    printf("%s is ACTIVE\n", event_name);
-  } else {
-    printf("%s is NOT-ACTIVE\n", event_name);
-  }
-
-  return active;
-}
-
-/* ------------------------------------------------------------------------
- * try_resolve
- * ------------------------------------------------------------------------
- * checks if the event_name is still unresolved; if it is, it then
- * resolves it, and checks the status of the event again to make sure
- * the event was actually resolved
- *
- * NOTE: all the special string manipulation is needed because the CLI
- * appends extra newline character to end of the user input; normally
- * do not have to do all this special string manipulation
- */
-void
-try_resolve(char *event_name)
-{
-  TSMgmtError ret;
-  char *name;
-
-  name = TSstrdup(event_name);
-  printf("[try_resolve] Resolving event: %s\n", name);
-
-  if (check_active(name)) { // resolve events
-    ret = TSEventResolve(name);
-    print_err("TSEventResolve", ret);
-    check_active(name); // should be non-active now
-  }
-
-  TSfree(name);
-}
-
-/* ------------------------------------------------------------------------
- * eventCallbackFn
- * ------------------------------------------------------------------------
- * the callback function; when called, it just prints out the name
- * of the event that was signalled
- */
-void
-eventCallbackFn(char *name, char *msg, int /* pri ATS_UNUSED */, void * /* data ATS_UNUSED */)
-{
-  printf("[eventCallbackFn] EVENT: %s, %s\n", name, msg);
-  return;
-}
-
-/* ------------------------------------------------------------------------
- * register_event_callback
- * ------------------------------------------------------------------------
- * registers the eventCallbackFn above for all events; this just means
- * that for any event that's signalled, the callback fn will also be called
- */
-void
-register_event_callback()
-{
-  TSMgmtError err;
-
-  printf("\n[register_event_callback] \n");
-  err = TSEventSignalCbRegister(nullptr, eventCallbackFn, nullptr);
-  print_err("TSEventSignalCbRegister", err);
-}
-
-/* ------------------------------------------------------------------------
- * unregister_event_callback
- * ------------------------------------------------------------------------
- * unregisters the eventCallbackFn above for all events; this just means
- * that it will remove this eventCallbackFn entirely so that for any
- * event called, the eventCallbackFn will NOT be called
- */
-void
-unregister_event_callback()
-{
-  TSMgmtError err;
-
-  printf("\n[unregister_event_callback]\n");
-  err = TSEventSignalCbUnregister(nullptr, eventCallbackFn);
-  print_err("TSEventSignalCbUnregister", err);
-}
-
-/***************************************************************************
- * Statistics
- ***************************************************************************/
-
-// generate dummy values for statistics
-void
-set_stats()
-{
-  TSActionNeedT action;
-
-  fprintf(stderr, "[set_stats] Set Dummy Stat Values\n");
-
-  TSRecordSetInt("proxy.process.http.user_agent_response_document_total_size", 100, &action);
-  TSRecordSetInt("proxy.process.http.user_agent_response_header_total_size", 100, &action);
-  TSRecordSetInt("proxy.process.http.current_client_connections", 100, &action);
-  TSRecordSetInt("proxy.process.http.current_client_transactions", 100, &action);
-  TSRecordSetInt("proxy.process.http.origin_server_response_document_total_size", 100, &action);
-  TSRecordSetInt("proxy.process.http.origin_server_response_header_total_size", 100, &action);
-  TSRecordSetInt("proxy.process.http.current_server_connections", 100, &action);
-  TSRecordSetInt("proxy.process.http.current_server_transactions", 100, &action);
-
-  TSRecordSetInt("proxy.node.proxy_running", 110, &action);
-  TSRecordSetInt("proxy.node.proxy_running", 110, &action);
-}
-
-void
-print_stats()
-{
-  TSInt i1, i2, i3, i4, i5, i6, i7, i8;
-
-  fprintf(stderr, "[print_stats]\n");
-
-  TSRecordGetInt("proxy.process.http.user_agent_response_document_total_size", &i1);
-  TSRecordGetInt("proxy.process.http.user_agent_response_header_total_size", &i2);
-  TSRecordGetInt("proxy.process.http.current_client_connections", &i3);
-  TSRecordGetInt("proxy.process.http.current_client_transactions", &i4);
-  TSRecordGetInt("proxy.process.http.origin_server_response_document_total_size", &i5);
-  TSRecordGetInt("proxy.process.http.origin_server_response_header_total_size", &i6);
-  TSRecordGetInt("proxy.process.http.current_server_connections", &i7);
-  TSRecordGetInt("proxy.process.http.current_server_transactions", &i8);
-
-  fprintf(stderr, "%" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "\n", i1,
-          i2, i3, i4, i5, i6, i7, i8);
-
-  TSRecordGetInt("proxy.node.proxy_running", &i4);
-  TSRecordGetInt("proxy.node.proxy_running", &i6);
-
-  fprintf(stderr, "%" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "\n", i1, i7, i2, i3, i4,
-          i5, i6);
-
-  fprintf(stderr, "PROCESS stats: \n");
-  fprintf(stderr, "%" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "\n", i1, i2, i3, i4);
-}
-
-void
-reset_stats()
-{
-  TSMgmtError err = TSStatsReset(nullptr);
-  print_err("TSStatsReset", err);
-  return;
-}
-
-void
-sync_test()
-{
-  TSActionNeedT action;
-
-  TSRecordSetString("proxy.config.proxy_name", "dorkface", &action);
-  printf("[TSRecordSetString] proxy.config.proxy_name \n\tAction Should: [%d]\n\tAction is    : [%d]\n", TS_ACTION_UNDEFINED,
-         action);
-
-  TSMgmtError ret;
-  if ((ret = TSProxyStateSet(TS_PROXY_OFF, TS_CACHE_CLEAR_NONE)) != TS_ERR_OKAY) {
-    printf("[TSProxyStateSet] turn off FAILED\n");
-  }
-  print_err("stop_TS", ret);
-}
-
-/* ########################################################################*/
-/* ------------------------------------------------------------------------
- * runInteractive
- * ------------------------------------------------------------------------
- * the loop that processes the commands inputted by user
- */
-static void
-runInteractive()
-{
-  char buf[512]; // holds request from interactive prompt
-
-  // process input from command line
-  while (true) {
-    // Display a prompt
-    printf("api_cli-> ");
-
-    // get input from command line
-    ATS_UNUSED_RETURN(fgets(buf, 512, stdin));
-
-    // check status of 'stdin' after reading
-    if (feof(stdin) != 0) {
-      printf("EXIT api_cli_remote\n");
-      return;
-    } else if (ferror(stdin) != 0) {
-      printf("EXIT api_cli_remote\n");
-      return;
-    }
-    // continue on newline
-    if (strcmp(buf, "\n") == 0) {
-      continue;
-    }
-    // exiting/quitting?
-    if (strcasecmp("quit\n", buf) == 0 || strcasecmp("exit\n", buf) == 0) {
-      // Don't wait for response LM
-      // exit(0);
-      return;
-    }
-    // check what operation user typed in
-    if (strstr(buf, "state")) {
-      print_proxy_state();
-    } else if (strncmp(buf, "start", 5) == 0) {
-      start_TS(buf);
-    } else if (strstr(buf, "stop")) {
-      stop_TS();
-    } else if (strstr(buf, "restart")) {
-      restart();
-    } else if (strstr(buf, "reconfig")) {
-      reconfigure();
-    } else if (strstr(buf, "records")) {
-      test_records();
-    } else if (strstr(buf, "err_recs")) {
-      test_error_records();
-    } else if (strstr(buf, "get_mlt")) {
-      test_record_get_mlt();
-    } else if (strstr(buf, "set_mlt")) {
-      test_record_set_mlt();
-    } else if (strstr(buf, "proxy.")) {
-      test_rec_get(buf);
-    } else if (strstr(buf, "active_events")) {
-      print_active_events();
-    } else if (strstr(buf, "MGMT_ALARM_")) {
-      try_resolve(buf);
-    } else if (strncmp(buf, "register", 8) == 0) {
-      register_event_callback();
-    } else if (strstr(buf, "unregister")) {
-      unregister_event_callback();
-    } else if (strstr(buf, "read_url")) {
-      test_read_url(true);
-    } else if (strstr(buf, "test_url")) {
-      test_read_url(false);
-    } else if (strstr(buf, "reset_stats")) {
-      reset_stats();
-    } else if (strstr(buf, "set_stats")) {
-      set_stats();
-    } else if (strstr(buf, "print_stats")) {
-      print_stats();
-    } else {
-      sync_test();
-    }
-
-  } // end while(1)
-
-} // end runInteractive
-
-/* ------------------------------------------------------------------------
- * main
- * ------------------------------------------------------------------------
- * Main entry point which connects the client to the API, does any
- * clean up on exit, and gets the interactive command-line running
- */
-int
-main(int /* argc ATS_UNUSED */, char ** /* argv ATS_UNUSED */)
-{
-  TSMgmtError ret;
-
-  if ((ret = TSInit(nullptr, TS_MGMT_OPT_DEFAULTS)) == TS_ERR_OKAY) {
-    runInteractive();
-    TSTerminate();
-    printf("END REMOTE API TEST\n");
-  } else {
-    print_err("main", ret);
-  }
-
-  return 0;
-} // end main()
diff --git a/mgmt/api/CoreAPI.cc b/mgmt/api/CoreAPI.cc
deleted file mode 100644
index 35cd826..0000000
--- a/mgmt/api/CoreAPI.cc
+++ /dev/null
@@ -1,924 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * CoreAPI.cc
- *
- * Implementation of many of TSMgmtAPI functions, but from local side.
- *
- *
- ***************************************************************************/
-#include <vector>
-
-#include "tscore/ink_platform.h"
-#include "tscore/ink_file.h"
-#include "tscore/ParseRules.h"
-#include "RecordsConfig.h"
-#include "Alarms.h"
-#include "MgmtUtils.h"
-#include "LocalManager.h"
-#include "FileManager.h"
-#include "ConfigManager.h"
-#include "WebMgmtUtils.h"
-#include "tscore/Diags.h"
-#include "ExpandingArray.h"
-
-#include "CoreAPI.h"
-#include "CoreAPIShared.h"
-#include "EventCallback.h"
-#include "tscore/I_Layout.h"
-#include "tscore/ink_cap.h"
-
-// global variable
-static CallbackTable *local_event_callbacks;
-
-extern FileManager *configFiles; // global in traffic_manager
-
-/*-------------------------------------------------------------------------
- * Init
- *-------------------------------------------------------------------------
- * performs any necessary initializations for the local API client,
- * eg. set up global structures; called by the TSMgmtAPI::TSInit()
- */
-TSMgmtError
-Init(const char * /* socket_path ATS_UNUSED */, TSInitOptionT options)
-{
-  // socket_path should be null; only applies to remote clients
-  if (0 == (options & TS_MGMT_OPT_NO_EVENTS)) {
-    local_event_callbacks = create_callback_table("local_callbacks");
-    if (!local_event_callbacks) {
-      return TS_ERR_SYS_CALL;
-    }
-  } else {
-    local_event_callbacks = nullptr;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * Terminate
- *-------------------------------------------------------------------------
- * performs any necessary cleanup of global structures, etc,
- * for the local API client,
- */
-TSMgmtError
-Terminate()
-{
-  delete_callback_table(local_event_callbacks);
-
-  return TS_ERR_OKAY;
-}
-
-/***************************************************************************
- * Control Operations
- ***************************************************************************/
-
-// bool ProxyShutdown()
-//
-//  Attempts to turn the proxy off.  Returns
-//    true if the proxy is off when the call returns
-//    and false if it is still on
-//
-static bool
-ProxyShutdown()
-{
-  int i = 0;
-
-  // Check to make sure that we are not already down
-  if (!lmgmt->processRunning()) {
-    return true;
-  }
-
-  lmgmt->processShutdown(false /* only shut down the proxy*/);
-
-  // Wait for awhile for shutdown to happen
-  do {
-    mgmt_sleep_sec(1);
-    i++;
-  } while (i < 10 && lmgmt->processRunning());
-
-  // See if we succeeded
-  if (lmgmt->processRunning()) {
-    return false;
-  } else {
-    return true;
-  }
-}
-/*-------------------------------------------------------------------------
- * ProxyStateGet
- *-------------------------------------------------------------------------
- * return TS_PROXY_OFF if  Traffic Server is off.
- * return TS_PROXY_ON if Traffic Server is on.
- */
-TSProxyStateT
-ProxyStateGet()
-{
-  if (!lmgmt->processRunning()) {
-    return TS_PROXY_OFF;
-  } else {
-    return TS_PROXY_ON;
-  }
-}
-
-/*-------------------------------------------------------------------------
- * ProxyStateSet
- *-------------------------------------------------------------------------
- * If state == TS_PROXY_ON, will turn on TS (unless it's already running).
- * If state == TS_PROXY_OFF, will turn off TS (unless it's already off).
- * tsArgs  - (optional) a string with space delimited options that user
- *            wants to start traffic Server with
- */
-TSMgmtError
-ProxyStateSet(TSProxyStateT state, TSCacheClearT clear)
-{
-  char tsArgs[MAX_BUF_SIZE];
-  char *proxy_options;
-
-  ink_zero(tsArgs);
-
-  switch (state) {
-  case TS_PROXY_OFF:
-    if (!ProxyShutdown()) { // from WebMgmtUtils
-      goto Lerror;          // unsuccessful shutdown
-    }
-    break;
-  case TS_PROXY_ON:
-    if (lmgmt->processRunning()) { // already on
-      break;
-    }
-
-    // Start with the default options from records.config.
-    if (RecGetRecordString_Xmalloc("proxy.config.proxy_binary_opts", &proxy_options) == REC_ERR_OKAY) {
-      if (max_records_entries ==
-          (REC_INTERNAL_RECORDS + REC_DEFAULT_API_RECORDS)) { // Default size, don't need to pass down to _server
-        snprintf(tsArgs, sizeof(tsArgs), "%s", proxy_options);
-      } else {
-        snprintf(tsArgs, sizeof(tsArgs), "%s --maxRecords %d", proxy_options, max_records_entries);
-      }
-      ats_free(proxy_options);
-    }
-
-    if (clear & TS_CACHE_CLEAR_CACHE) {
-      ink_strlcat(tsArgs, " -K", sizeof(tsArgs));
-    }
-
-    if (clear & TS_CACHE_CLEAR_HOSTDB) {
-      ink_strlcat(tsArgs, " -k", sizeof(tsArgs));
-    }
-
-    mgmt_log("[ProxyStateSet] Traffic Server Args: '%s %s'\n", lmgmt->proxy_options.c_str(), tsArgs);
-
-    lmgmt->run_proxy = true;
-    lmgmt->listenForProxy();
-    if (!lmgmt->startProxy(tsArgs)) {
-      goto Lerror;
-    }
-
-    break;
-
-  default:
-    goto Lerror;
-  }
-
-  return TS_ERR_OKAY;
-
-Lerror:
-  return TS_ERR_FAIL; /* failed to set proxy state */
-}
-
-#if TS_USE_REMOTE_UNWINDING
-
-#include <libunwind.h>
-#include <libunwind-ptrace.h>
-#include <sys/ptrace.h>
-#include <cxxabi.h>
-
-using threadlist = std::vector<pid_t>;
-
-static threadlist
-threads_for_process(pid_t proc)
-{
-  DIR *dir             = nullptr;
-  struct dirent *entry = nullptr;
-
-  char path[64];
-  threadlist threads;
-
-  if (snprintf(path, sizeof(path), "/proc/%ld/task", static_cast<long>(proc)) >= static_cast<int>(sizeof(path))) {
-    goto done;
-  }
-
-  dir = opendir(path);
-  if (dir == nullptr) {
-    goto done;
-  }
-
-  while ((entry = readdir(dir))) {
-    pid_t threadid;
-
-    if (isdot(entry->d_name) || isdotdot(entry->d_name)) {
-      continue;
-    }
-
-    threadid = strtol(entry->d_name, nullptr, 10);
-    if (threadid > 0) {
-      threads.push_back(threadid);
-      Debug("backtrace", "found thread %ld", (long)threadid);
-    }
-  }
-
-done:
-  if (dir) {
-    closedir(dir);
-  }
-
-  return threads;
-}
-
-static void
-backtrace_for_thread(pid_t threadid, TextBuffer &text)
-{
-  int status;
-  unw_addr_space_t addr_space = nullptr;
-  unw_cursor_t cursor;
-  void *ap       = nullptr;
-  pid_t target   = -1;
-  unsigned level = 0;
-
-  // First, attach to the child, causing it to stop.
-  status = ptrace(PTRACE_ATTACH, threadid, 0, 0);
-  if (status < 0) {
-    Debug("backtrace", "ptrace(ATTACH, %ld) -> %s (%d)", (long)threadid, strerror(errno), errno);
-    return;
-  }
-
-  // Wait for it to stop (XXX should be a timed wait ...)
-  target = waitpid(threadid, &status, __WALL | WUNTRACED);
-  Debug("backtrace", "waited for target %ld, found PID %ld, %s", (long)threadid, (long)target,
-        WIFSTOPPED(status) ? "STOPPED" : "???");
-  if (target < 0) {
-    goto done;
-  }
-
-  ap = _UPT_create(threadid);
-  Debug("backtrace", "created UPT %p", ap);
-  if (ap == nullptr) {
-    goto done;
-  }
-
-  addr_space = unw_create_addr_space(&_UPT_accessors, 0 /* byteorder */);
-  Debug("backtrace", "created address space %p", addr_space);
-  if (addr_space == nullptr) {
-    goto done;
-  }
-
-  status = unw_init_remote(&cursor, addr_space, ap);
-  Debug("backtrace", "unw_init_remote(...) -> %d", status);
-  if (status != 0) {
-    goto done;
-  }
-
-  while (unw_step(&cursor) > 0) {
-    unw_word_t ip;
-    unw_word_t offset;
-    char buf[256];
-
-    unw_get_reg(&cursor, UNW_REG_IP, &ip);
-
-    if (unw_get_proc_name(&cursor, buf, sizeof(buf), &offset) == 0) {
-      int status;
-      char *name = abi::__cxa_demangle(buf, nullptr, nullptr, &status);
-      text.format("%-4u 0x%016llx %s + %p\n", level, static_cast<unsigned long long>(ip), name ? name : buf, (void *)offset);
-      free(name);
-    } else {
-      text.format("%-4u 0x%016llx 0x0 + %p\n", level, static_cast<unsigned long long>(ip), (void *)offset);
-    }
-
-    ++level;
-  }
-
-done:
-  if (addr_space) {
-    unw_destroy_addr_space(addr_space);
-  }
-
-  if (ap) {
-    _UPT_destroy(ap);
-  }
-
-  status = ptrace(PTRACE_DETACH, target, NULL, NULL);
-  Debug("backtrace", "ptrace(DETACH, %ld) -> %d (errno %d)", (long)target, status, errno);
-}
-
-TSMgmtError
-ServerBacktrace(unsigned /* options */, char **trace)
-{
-  *trace = nullptr;
-
-  // Unfortunately, we need to be privileged here. We either need to be root or to be holding
-  // the CAP_SYS_PTRACE capability. Even though we are the parent traffic_manager, it is not
-  // traceable without privilege because the process credentials do not match.
-  ElevateAccess access(ElevateAccess::TRACE_PRIVILEGE);
-  threadlist threads(threads_for_process(lmgmt->watched_process_pid));
-  TextBuffer text(0);
-
-  Debug("backtrace", "tracing %zd threads for traffic_server PID %ld", threads.size(), (long)lmgmt->watched_process_pid);
-
-  for (auto threadid : threads) {
-    Debug("backtrace", "tracing thread %ld", (long)threadid);
-    // Get the thread name using /proc/PID/comm
-    ats_scoped_fd fd;
-    char threadname[128];
-
-    snprintf(threadname, sizeof(threadname), "/proc/%ld/comm", static_cast<long>(threadid));
-    fd = open(threadname, O_RDONLY);
-    if (fd >= 0) {
-      text.format("Thread %ld, ", static_cast<long>(threadid));
-      text.readFromFD(fd);
-      text.chomp();
-    } else {
-      text.format("Thread %ld", static_cast<long>(threadid));
-    }
-
-    text.format(":\n");
-
-    backtrace_for_thread(threadid, text);
-    text.format("\n");
-  }
-
-  *trace = text.release();
-  return TS_ERR_OKAY;
-}
-
-#else /* TS_USE_REMOTE_UNWINDING */
-
-TSMgmtError
-ServerBacktrace(unsigned /* options */, char **trace)
-{
-  *trace = nullptr;
-  return TS_ERR_NOT_SUPPORTED;
-}
-
-#endif
-
-/*-------------------------------------------------------------------------
- * Reconfigure
- *-------------------------------------------------------------------------
- * Rereads configuration files
- */
-TSMgmtError
-Reconfigure()
-{
-  configFiles->rereadConfig();                              // TM rereads
-  lmgmt->signalEvent(MGMT_EVENT_PLUGIN_CONFIG_UPDATE, "*"); // TS rereads
-  RecSetRecordInt("proxy.node.config.reconfigure_time", time(nullptr), REC_SOURCE_DEFAULT);
-  RecSetRecordInt("proxy.node.config.reconfigure_required", 0, REC_SOURCE_DEFAULT);
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * Restart
- *-------------------------------------------------------------------------
- * Restarts Traffic Manager. Traffic Cop must be running in order to
- * restart Traffic Manager!!
- */
-TSMgmtError
-Restart(unsigned options)
-{
-  lmgmt->mgmt_shutdown_triggered_at = time(nullptr);
-  lmgmt->mgmt_shutdown_outstanding  = (options & TS_RESTART_OPT_DRAIN) ? MGMT_PENDING_IDLE_RESTART : MGMT_PENDING_RESTART;
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * Bouncer
- *-------------------------------------------------------------------------
- * Bounces traffic_server process(es).
- */
-TSMgmtError
-Bounce(unsigned options)
-{
-  lmgmt->mgmt_shutdown_triggered_at = time(nullptr);
-  lmgmt->mgmt_shutdown_outstanding  = (options & TS_RESTART_OPT_DRAIN) ? MGMT_PENDING_IDLE_BOUNCE : MGMT_PENDING_BOUNCE;
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * Stop
- *-------------------------------------------------------------------------
- * Stops traffic_server process(es).
- */
-TSMgmtError
-Stop(unsigned options)
-{
-  lmgmt->mgmt_shutdown_triggered_at = time(nullptr);
-  lmgmt->mgmt_shutdown_outstanding  = (options & TS_STOP_OPT_DRAIN) ? MGMT_PENDING_IDLE_STOP : MGMT_PENDING_STOP;
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * Drain
- *-------------------------------------------------------------------------
- * Drain requests of traffic_server
- */
-TSMgmtError
-Drain(unsigned options)
-{
-  switch (options) {
-  case TS_DRAIN_OPT_NONE:
-    lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_DRAIN;
-    break;
-  case TS_DRAIN_OPT_IDLE:
-    lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_IDLE_DRAIN;
-    break;
-  case TS_DRAIN_OPT_UNDO:
-    lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_UNDO_DRAIN;
-    break;
-  default:
-    ink_release_assert(!"Not expected to reach here");
-  }
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * StorageDeviceCmdOffline
- *-------------------------------------------------------------------------
- * Disable a storage device.
- * [amc] I don't think this is called but is required because of the way the
- * CoreAPI is linked (it must match the remote CoreAPI signature so compiling
- * this source or CoreAPIRemote.cc yields the same set of symbols).
- */
-TSMgmtError
-StorageDeviceCmdOffline(const char *dev)
-{
-  lmgmt->signalEvent(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, dev);
-  return TS_ERR_OKAY;
-}
-/*-------------------------------------------------------------------------
- * Lifecycle Message
- *-------------------------------------------------------------------------
- * Signal plugins.
- */
-TSMgmtError
-LifecycleMessage(const char *tag, void const *data, size_t data_size)
-{
-  ink_release_assert(!"Not expected to reach here");
-  lmgmt->signalEvent(MGMT_EVENT_LIFECYCLE_MESSAGE, tag);
-  return TS_ERR_OKAY;
-}
-/**************************************************************************
- * RECORD OPERATIONS
- *************************************************************************/
-/*-------------------------------------------------------------------------
- * MgmtRecordGet
- *-------------------------------------------------------------------------
- * rec_ele has allocated memory already but with all empty fields.
- * The record info associated with rec_name will be returned in rec_ele.
- */
-TSMgmtError
-MgmtRecordGet(const char *rec_name, TSRecordEle *rec_ele)
-{
-  RecDataT rec_type;
-  char rec_val[MAX_BUF_SIZE];
-  char *str_val;
-  MgmtIntCounter counter_val;
-  MgmtInt int_val;
-
-  Debug("RecOp", "[MgmtRecordGet] Start");
-
-  // initialize the record name
-  rec_ele->rec_name = ats_strdup(rec_name);
-  memset(rec_val, 0, MAX_BUF_SIZE);
-
-  // get variable type; returns INVALID if invalid rec_name
-  rec_type = varType(rec_name);
-  switch (rec_type) {
-  case RECD_COUNTER:
-    rec_ele->rec_type = TS_REC_COUNTER;
-    if (!varCounterFromName(rec_name, &(counter_val))) {
-      return TS_ERR_FAIL;
-    }
-    rec_ele->valueT.counter_val = static_cast<TSCounter>(counter_val);
-
-    Debug("RecOp", "[MgmtRecordGet] Get Counter Var %s = %" PRId64 "", rec_ele->rec_name, rec_ele->valueT.counter_val);
-    break;
-
-  case RECD_INT:
-    rec_ele->rec_type = TS_REC_INT;
-    if (!varIntFromName(rec_name, &(int_val))) {
-      return TS_ERR_FAIL;
-    }
-    rec_ele->valueT.int_val = static_cast<TSInt>(int_val);
-
-    Debug("RecOp", "[MgmtRecordGet] Get Int Var %s = %" PRId64 "", rec_ele->rec_name, rec_ele->valueT.int_val);
-    break;
-
-  case RECD_FLOAT:
-    rec_ele->rec_type = TS_REC_FLOAT;
-    if (!varFloatFromName(rec_name, &(rec_ele->valueT.float_val))) {
-      return TS_ERR_FAIL;
-    }
-
-    Debug("RecOp", "[MgmtRecordGet] Get Float Var %s = %f", rec_ele->rec_name, rec_ele->valueT.float_val);
-    break;
-
-  case RECD_STRING:
-    if (!varStrFromName(rec_name, rec_val, MAX_BUF_SIZE)) {
-      return TS_ERR_FAIL;
-    }
-
-    if (rec_val[0] != '\0') { // non-NULL string value
-      // allocate memory & duplicate string value
-      str_val = ats_strdup(rec_val);
-    } else {
-      str_val = ats_strdup("NULL");
-    }
-
-    rec_ele->rec_type          = TS_REC_STRING;
-    rec_ele->valueT.string_val = str_val;
-    Debug("RecOp", "[MgmtRecordGet] Get String Var %s = %s", rec_ele->rec_name, rec_ele->valueT.string_val);
-    break;
-
-  default: // UNKNOWN TYPE
-    Debug("RecOp", "[MgmtRecordGet] Get Failed : %d is Unknown Var type %s", rec_type, rec_name);
-    return TS_ERR_FAIL;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-// This is not implemented in the Core side of the API because we don't want
-// to buffer up all the matching records in memory. We stream the records
-// directory onto the management socket in handle_record_match(). This stub
-// is just here for link time dependencies.
-TSMgmtError
-MgmtRecordGetMatching(const char * /* regex */, TSList /* rec_vals */)
-{
-  return TS_ERR_FAIL;
-}
-
-TSMgmtError
-MgmtConfigRecordDescribe(const char * /* rec_name */, unsigned /* flags */, TSConfigRecordDescription * /* val */)
-{
-  return TS_ERR_NOT_SUPPORTED;
-}
-
-TSMgmtError
-MgmtConfigRecordDescribeMatching(const char *, unsigned, TSList)
-{
-  return TS_ERR_NOT_SUPPORTED;
-}
-
-/*-------------------------------------------------------------------------
- * reads the RecordsConfig info to determine which type of action is needed
- * when the record rec_name is changed; if the rec_name is invalid,
- * then returns TS_ACTION_UNDEFINED
- */
-TSActionNeedT
-determine_action_need(const char *rec_name)
-{
-  RecUpdateT update_t;
-
-  if (REC_ERR_OKAY != RecGetRecordUpdateType(rec_name, &update_t)) {
-    return TS_ACTION_UNDEFINED;
-  }
-
-  switch (update_t) {
-  case RECU_NULL: // default:don't know behaviour
-    return TS_ACTION_UNDEFINED;
-
-  case RECU_DYNAMIC: // update dynamically by rereading config files
-    return TS_ACTION_RECONFIGURE;
-
-  case RECU_RESTART_TS: // requires TS restart
-    return TS_ACTION_RESTART;
-
-  case RECU_RESTART_TM: // requires TM/TS restart
-    return TS_ACTION_RESTART;
-
-  default: // shouldn't get here actually
-    return TS_ACTION_UNDEFINED;
-  }
-
-  return TS_ACTION_UNDEFINED; // ERROR
-}
-
-/*-------------------------------------------------------------------------
- * MgmtRecordSet
- *-------------------------------------------------------------------------
- * Uses bool WebMgmtUtils::varSetFromStr(const char*, const char* )
- * Sets the named local manager variable from the value string
- * passed in.  Does the appropriate type conversion on
- * value string to get it to the type of the local manager
- * variable
- *
- *  returns true if the variable was successfully set
- *   and false otherwise
- */
-TSMgmtError
-MgmtRecordSet(const char *rec_name, const char *val, TSActionNeedT *action_need)
-{
-  Debug("RecOp", "[MgmtRecordSet] Start");
-
-  if (!rec_name || !val || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  *action_need = determine_action_need(rec_name);
-
-  if (recordValidityCheck(rec_name, val)) {
-    if (varSetFromStr(rec_name, val)) {
-      return TS_ERR_OKAY;
-    }
-  }
-
-  return TS_ERR_FAIL;
-}
-
-/*-------------------------------------------------------------------------
- * MgmtRecordSetInt
- *-------------------------------------------------------------------------
- * Use the record's name to look up the record value and its type.
- * Returns TS_ERR_FAIL if the type is not a valid integer.
- * Converts the integer value to a string and call MgmtRecordSet
- */
-TSMgmtError
-MgmtRecordSetInt(const char *rec_name, MgmtInt int_val, TSActionNeedT *action_need)
-{
-  if (!rec_name || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  // convert int value to string for validity check
-  char str_val[MAX_RECORD_SIZE];
-
-  memset(str_val, 0, MAX_RECORD_SIZE);
-  snprintf(str_val, sizeof(str_val), "%" PRId64 "", int_val);
-
-  return MgmtRecordSet(rec_name, str_val, action_need);
-}
-
-/*-------------------------------------------------------------------------
- * MgmtRecordSetCounter
- *-------------------------------------------------------------------------
- * converts the counter_val to a string and uses MgmtRecordSet
- */
-TSMgmtError
-MgmtRecordSetCounter(const char *rec_name, MgmtIntCounter counter_val, TSActionNeedT *action_need)
-{
-  if (!rec_name || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  // convert int value to string for validity check
-  char str_val[MAX_RECORD_SIZE];
-
-  memset(str_val, 0, MAX_RECORD_SIZE);
-  snprintf(str_val, sizeof(str_val), "%" PRId64 "", counter_val);
-
-  return MgmtRecordSet(rec_name, str_val, action_need);
-}
-
-/*-------------------------------------------------------------------------
- * MgmtRecordSetFloat
- *-------------------------------------------------------------------------
- * converts the float value to string (to do record validity check)
- * and calls MgmtRecordSet
- */
-TSMgmtError
-MgmtRecordSetFloat(const char *rec_name, MgmtFloat float_val, TSActionNeedT *action_need)
-{
-  if (!rec_name || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  // convert float value to string for validity check
-  char str_val[MAX_RECORD_SIZE];
-
-  memset(str_val, 0, MAX_RECORD_SIZE);
-  snprintf(str_val, sizeof(str_val), "%f", float_val);
-
-  return MgmtRecordSet(rec_name, str_val, action_need);
-}
-
-/*-------------------------------------------------------------------------
- * MgmtRecordSetString
- *-------------------------------------------------------------------------
- * The string value is copied so it's okay to free the string later
- */
-TSMgmtError
-MgmtRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT *action_need)
-{
-  return MgmtRecordSet(rec_name, string_val, action_need);
-}
-
-/**************************************************************************
- * EVENTS
- *************************************************************************/
-/*-------------------------------------------------------------------------
- * EventSignal
- *-------------------------------------------------------------------------
- * LAN: THIS FUNCTION IS HACKED AND INCOMPLETE!!!!!
- * with the current alarm processor system, the argument list is NOT
- * used; a set description is associated with each alarm already;
- * be careful because this alarm description is used to keep track
- * of alarms in the current alarm processor
- */
-TSMgmtError
-EventSignal(const char * /* event_name ATS_UNUSED */, va_list /* ap ATS_UNUSED */)
-{
-  // char *text;
-  // int id;
-
-  // id = get_event_id(event_name);
-  // text = get_event_text(event_name);
-  // lmgmt->alarm_keeper->signalAlarm(id, text, NULL);
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * EventResolve
- *-------------------------------------------------------------------------
- * Resolves the event of the given event_name. If the event is already
- * unresolved, just return TS_ERR_OKAY.
-
- */
-TSMgmtError
-EventResolve(const char *event_name)
-{
-  alarm_t a;
-
-  if (!event_name) {
-    return TS_ERR_PARAMS;
-  }
-
-  a = get_event_id(event_name);
-  lmgmt->alarm_keeper->resolveAlarm(a);
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * ActiveEventGetMlt
- *-------------------------------------------------------------------------
- * returns failure, and an incomplete active_alarms list if any of
- * functions fail for a single event
- * note: returns list of local alarms at that instant of fn call (snapshot)
- */
-TSMgmtError
-ActiveEventGetMlt(LLQ *active_events)
-{
-  if (!active_events) {
-    return TS_ERR_PARAMS;
-  }
-
-  // Alarms stores a hashtable of all active alarms where:
-  // key = alarm_t,
-  // value = alarm_description defined in Alarms.cc alarmText[] array
-  std::unordered_map<std::string, Alarm *> const &event_ht = lmgmt->alarm_keeper->getLocalAlarms();
-
-  // iterate through hash-table and insert event_name's into active_events list
-  for (auto &&it : event_ht) {
-    // convert key to int; insert into llQ
-    int event_id     = ink_atoi(it.first.c_str());
-    char *event_name = get_event_name(event_id);
-    if (event_name) {
-      if (!enqueue(active_events, event_name)) { // returns true if successful
-        return TS_ERR_FAIL;
-      }
-    }
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * EventIsActive
- *-------------------------------------------------------------------------
- * Sets *is_current to true if the event named event_name is currently
- * unresolved; otherwise sets *is_current to false.
- */
-TSMgmtError
-EventIsActive(const char *event_name, bool *is_current)
-{
-  alarm_t a;
-
-  if (!event_name || !is_current) {
-    return TS_ERR_PARAMS;
-  }
-
-  a = get_event_id(event_name);
-  // consider an invalid event_name an error
-  if (a < 0) {
-    return TS_ERR_PARAMS;
-  }
-  if (lmgmt->alarm_keeper->isCurrentAlarm(a)) {
-    *is_current = true; // currently an event
-  } else {
-    *is_current = false;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * EventSignalCbRegister
- *-------------------------------------------------------------------------
- * This differs from the remote side callback registered. Technically, I think
- * we need to redesign the alarm processor before we can handle the callback
- * functionality we want to accomplish. Because currently the alarm processor
- * only allow registering callbacks for general alarms.
- * Mimic remote side and have a separate structure (eg. hashtable) of
- * event callback functions for each type of event. The functions are also
- * stored in the hashtable, not in the TM alarm processor model
- */
-TSMgmtError
-EventSignalCbRegister(const char *event_name, TSEventSignalFunc func, void *data)
-{
-  return cb_table_register(local_event_callbacks, event_name, func, data, nullptr);
-}
-
-/*-------------------------------------------------------------------------
- * EventSignalCbUnregister
- *-------------------------------------------------------------------------
- * Removes the callback function from the local side CallbackTable
- */
-TSMgmtError
-EventSignalCbUnregister(const char *event_name, TSEventSignalFunc func)
-{
-  return cb_table_unregister(local_event_callbacks, event_name, func);
-}
-
-/*-------------------------------------------------------------------------
- * HostStatusSetDown
- *-------------------------------------------------------------------------
- * Sets the HOST status to Down
- *
- * 'marshalled_req' is marshalled here, (host_name and down_time, na).
- * 'len' is the length of the 'req' marshaled data.
- * 'na' unused.
- */
-TSMgmtError
-HostStatusSetDown(const char *marshalled_req, int len, const char *na)
-{
-  lmgmt->hostStatusSetDown(marshalled_req, len);
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * HostStatusSetUp
- *-------------------------------------------------------------------------
- * Sets the HOST status to Up
- *
- * 'marshalled_req' is marshalled here, host_name.
- * 'len' is the length of 'req'
- * 'na' unused.
- */
-TSMgmtError
-HostStatusSetUp(const char *marshalled_req, int len, const char *na)
-{
-  lmgmt->hostStatusSetUp(marshalled_req, len);
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * StatsReset
- *-------------------------------------------------------------------------
- * Iterates through the RecordsConfig table, and for all stats
- * (type PROCESS, NODE, CLUSTER), sets them back to their default value
- * If one stat fails to be set correctly, then continues onto next one,
- * but will return TS_ERR_FAIL. Only returns TS_ERR_OKAY if all
- * stats are set back to defaults successfully.
- */
-TSMgmtError
-StatsReset(const char *name)
-{
-  lmgmt->clearStats(name);
-  return TS_ERR_OKAY;
-}
diff --git a/mgmt/api/CoreAPI.h b/mgmt/api/CoreAPI.h
deleted file mode 100644
index aa4a444..0000000
--- a/mgmt/api/CoreAPI.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-#pragma once
-
-#include <cstdarg> // for va_list
-
-#include "tscore/ink_llqueue.h"
-#include "MgmtDefs.h" // MgmtInt, MgmtFloat, etc
-
-#include "mgmtapi.h"
-#include "tscore/Tokenizer.h"
-
-// for buffer used temporarily to parse incoming commands.
-#ifndef MAX_BUF_SIZE
-#define MAX_BUF_SIZE 4098
-#endif
-
-TSMgmtError Init(const char *socket_path = nullptr, TSInitOptionT options = TS_MGMT_OPT_DEFAULTS);
-TSMgmtError Terminate();
-
-/***************************************************************************
- * Control Operations
- ***************************************************************************/
-TSProxyStateT ProxyStateGet();
-TSMgmtError ProxyStateSet(TSProxyStateT state, TSCacheClearT clear);
-TSMgmtError ServerBacktrace(unsigned options, char **trace);
-
-TSMgmtError Reconfigure();                                                         // TS reread config files
-TSMgmtError Restart(unsigned options);                                             // restart TM
-TSMgmtError Bounce(unsigned options);                                              // restart traffic_server
-TSMgmtError Stop(unsigned options);                                                // stop traffic_server
-TSMgmtError Drain(unsigned options);                                               // drain requests of traffic_server
-TSMgmtError StorageDeviceCmdOffline(const char *dev);                              // Storage device operation.
-TSMgmtError LifecycleMessage(const char *tag, void const *data, size_t data_size); // Lifecycle alert to plugins.
-
-/***************************************************************************
- * Record Operations
- ***************************************************************************/
-/* For remote implementation of this interface, these functions will have
-   to marshal/unmarshal and send request across the network */
-TSMgmtError MgmtRecordGet(const char *rec_name, TSRecordEle *rec_ele);
-
-TSMgmtError MgmtRecordSet(const char *rec_name, const char *val, TSActionNeedT *action_need);
-TSMgmtError MgmtRecordSetInt(const char *rec_name, MgmtInt int_val, TSActionNeedT *action_need);
-TSMgmtError MgmtRecordSetCounter(const char *rec_name, MgmtIntCounter counter_val, TSActionNeedT *action_need);
-TSMgmtError MgmtRecordSetFloat(const char *rec_name, MgmtFloat float_val, TSActionNeedT *action_need);
-TSMgmtError MgmtRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT *action_need);
-TSMgmtError MgmtRecordGetMatching(const char *regex, TSList rec_vals);
-
-TSMgmtError MgmtConfigRecordDescribe(const char *rec_name, unsigned flags, TSConfigRecordDescription *val);
-TSMgmtError MgmtConfigRecordDescribeMatching(const char *regex, unsigned flags, TSList rec_vals);
-
-/***************************************************************************
- * Events
- ***************************************************************************/
-
-TSMgmtError EventSignal(const char *event_name, va_list ap);
-TSMgmtError EventResolve(const char *event_name);
-TSMgmtError ActiveEventGetMlt(LLQ *active_events);
-TSMgmtError EventIsActive(const char *event_name, bool *is_current);
-TSMgmtError EventSignalCbRegister(const char *event_name, TSEventSignalFunc func, void *data);
-TSMgmtError EventSignalCbUnregister(const char *event_name, TSEventSignalFunc func);
-
-TSMgmtError HostStatusSetDown(const char *host_name, int down_time, const char *reason);
-TSMgmtError HostStatusSetUp(const char *host_name, int down_time, const char *reason);
-TSMgmtError StatsReset(const char *name = nullptr);
diff --git a/mgmt/api/CoreAPIRemote.cc b/mgmt/api/CoreAPIRemote.cc
deleted file mode 100644
index 28a9050..0000000
--- a/mgmt/api/CoreAPIRemote.cc
+++ /dev/null
@@ -1,1071 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: CoreAPIRemote.cc
- * Purpose: Implementation of CoreAPI.h interface but from remote client
- *          perspective, so must also add networking calls. Basically, any
- *          TSMgmtAPI calls which are "special" for remote clients
- *          need to be implemented here.
- * Note: For remote implementation of this interface, most functions will:
- *  1) marshal: create the message to send across network
- *  2) connect and send request
- *  3) unmarshal: parse the reply (checking for TSMgmtError)
- *
- * Created: lant
- *
- ***************************************************************************/
-
-#include "tscore/ink_config.h"
-#include "tscore/ink_defs.h"
-#include <strings.h>
-#include "tscore/ink_string.h"
-#include "tscore/I_Layout.h"
-#include "tscore/ParseRules.h"
-#include "tscore/ink_memory.h"
-#include "CoreAPI.h"
-#include "CoreAPIShared.h"
-#include "NetworkUtilsRemote.h"
-
-#include "EventCallback.h"
-#include "MgmtMarshall.h"
-
-// forward declarations
-static TSMgmtError send_and_parse_list(OpType op, LLQ *list);
-static TSMgmtError mgmt_record_set(const char *rec_name, const char *rec_val, TSActionNeedT *action_need);
-
-// global variables
-// need to store the thread id associated with socket_test_thread
-// in case we want to  explicitly stop/cancel the testing thread
-ink_thread ts_test_thread;
-ink_thread ts_event_thread;
-TSInitOptionT ts_init_options;
-
-/***************************************************************************
- * Helper Functions
- ***************************************************************************/
-
-/*-------------------------------------------------------------------------
- * send_and_parse_list (helper function)
- *-------------------------------------------------------------------------
- * helper function used by operations which only require sending a simple
- * operation type and parsing a string delimited list
- * (delimited with REMOTE_DELIM_STR) and storing the tokens in the list
- * parameter
- */
-static TSMgmtError
-send_and_parse_list(OpType op, LLQ *list)
-{
-  TSMgmtError ret;
-  const char *tok;
-  Tokenizer tokens(REMOTE_DELIM_STR);
-  tok_iter_state i_state;
-
-  OpType optype = op;
-  MgmtMarshallInt err;
-  MgmtMarshallData reply    = {nullptr, 0};
-  MgmtMarshallString strval = nullptr;
-
-  if (!list) {
-    return TS_ERR_PARAMS;
-  }
-
-  // create and send request
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, op, &optype);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, op, &err, &strval);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  if (err != TS_ERR_OKAY) {
-    ret = static_cast<TSMgmtError>(err);
-    goto done;
-  }
-
-  // tokenize the strval and put into LLQ; use Tokenizer
-  tokens.Initialize(strval, COPY_TOKS);
-  tok = tokens.iterFirst(&i_state);
-  while (tok != nullptr) {
-    enqueue(list, ats_strdup(tok)); // add token to LLQ
-    tok = tokens.iterNext(&i_state);
-  }
-
-  ret = TS_ERR_OKAY;
-
-done:
-  ats_free(reply.ptr);
-  ats_free(strval);
-  return ret;
-}
-
-/*-------------------------------------------------------------------------
- * mgmt_record_set (helper function)
- *-------------------------------------------------------------------------
- * Helper function for all Set functions:
- * NOTE: regardless of the type of the record being set,
- * it is converted to a string. Then on the local side, the
- * CoreAPI::MgmtRecordSet function will do the appropriate type
- * conversion from the string to the record's type (eg. MgmtInt, MgmtString..)
- * Hence, on the local side, don't have to worry about typecasting a
- * void*. Just read out the string from socket and pass it MgmtRecordSet.
- */
-static TSMgmtError
-mgmt_record_set(const char *rec_name, const char *rec_val, TSActionNeedT *action_need)
-{
-  TSMgmtError ret;
-
-  OpType optype            = OpType::RECORD_SET;
-  MgmtMarshallString name  = const_cast<MgmtMarshallString>(rec_name);
-  MgmtMarshallString value = const_cast<MgmtMarshallString>(rec_val);
-
-  MgmtMarshallData reply = {nullptr, 0};
-  MgmtMarshallInt err;
-  MgmtMarshallInt action = TS_ACTION_UNDEFINED;
-
-  if (!rec_name || !rec_val || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  *action_need = TS_ACTION_UNDEFINED;
-
-  // create and send request
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RECORD_SET, &optype, &name, &value);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, OpType::RECORD_SET, &err, &action);
-  ats_free(reply.ptr);
-
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  if (err != TS_ERR_OKAY) {
-    return static_cast<TSMgmtError>(err);
-  }
-
-  *action_need = static_cast<TSActionNeedT>(action);
-  return TS_ERR_OKAY;
-}
-
-/***************************************************************************
- * SetUp Operations
- ***************************************************************************/
-TSMgmtError
-Init(const char *socket_path, TSInitOptionT options)
-{
-  TSMgmtError err = TS_ERR_OKAY;
-
-  ts_init_options = options;
-
-  // XXX This should use RecConfigReadRuntimeDir(), but that's not linked into the management
-  // libraries. The caller has to pass down the right socket path :(
-  if (!socket_path) {
-    Layout::create();
-    socket_path = Layout::get()->runtimedir.c_str();
-  }
-
-  // store socket_path
-  set_socket_paths(socket_path);
-
-  // need to ignore SIGPIPE signal; in the case that TM is restarted
-  signal(SIGPIPE, SIG_IGN);
-
-  // EVENT setup - initialize callback queue
-  if (0 == (ts_init_options & TS_MGMT_OPT_NO_EVENTS)) {
-    remote_event_callbacks = create_callback_table("remote_callbacks");
-    if (!remote_event_callbacks) {
-      return TS_ERR_SYS_CALL;
-    }
-  } else {
-    remote_event_callbacks = nullptr;
-  }
-
-  // try to connect to traffic manager
-  // do this last so that everything else on client side is set up even if
-  // connection fails; this might happen if client is set up and running
-  // before TM
-  err = ts_connect();
-  if (err != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  // if connected, create event thread that listens for events from TM
-  if (0 == (ts_init_options & TS_MGMT_OPT_NO_EVENTS)) {
-    ink_thread_create(&ts_event_thread, event_poll_thread_main, &event_socket_fd, 0, 0, nullptr);
-  } else {
-    ts_event_thread = ink_thread_null();
-  }
-
-END:
-
-  // create thread that periodically checks the socket connection
-  // with TM alive - reconnects if not alive
-  if (0 == (ts_init_options & TS_MGMT_OPT_NO_SOCK_TESTS)) {
-    ink_thread_create(&ts_test_thread, socket_test_thread, nullptr, 0, 0, nullptr);
-  } else {
-    ts_test_thread = ink_thread_null();
-  }
-
-  return err;
-}
-
-// does clean up for remote API client; destroy structures and disconnects
-TSMgmtError
-Terminate()
-{
-  TSMgmtError err;
-
-  if (remote_event_callbacks) {
-    delete_callback_table(remote_event_callbacks);
-  }
-
-  // be sure to do this before reset socket_fd's
-  err = disconnect();
-  if (err != TS_ERR_OKAY) {
-    return err;
-  }
-
-  // cancel the listening socket thread
-  // it's important to call this before setting paths to NULL because the
-  // socket_test_thread actually will try to reconnect() and this function
-  // will seg fault if the socket paths are NULL while it is connecting;
-  // the thread will be cancelled at a cancellation point in the
-  // socket_test_thread, eg. sleep
-  if (ts_test_thread) {
-    ink_thread_cancel(ts_test_thread);
-  }
-  if (ts_event_thread) {
-    ink_thread_cancel(ts_event_thread);
-  }
-
-  // Before clear, we should confirm these
-  // two threads have finished. Or the clear
-  // operation may lead them crash.
-  if (ts_test_thread) {
-    ink_thread_join(ts_test_thread);
-  }
-  if (ts_event_thread) {
-    ink_thread_join(ts_event_thread);
-  }
-
-  // Clear operation
-  ts_test_thread  = ink_thread_null();
-  ts_event_thread = ink_thread_null();
-  set_socket_paths(nullptr); // clear the socket_path
-
-  return TS_ERR_OKAY;
-}
-
-/***************************************************************************
- * Control Operations
- ***************************************************************************/
-TSProxyStateT
-ProxyStateGet()
-{
-  TSMgmtError ret;
-  OpType optype          = OpType::PROXY_STATE_GET;
-  MgmtMarshallData reply = {nullptr, 0};
-  MgmtMarshallInt err;
-  MgmtMarshallInt state;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::PROXY_STATE_GET, &optype);
-  if (ret != TS_ERR_OKAY) {
-    return TS_PROXY_UNDEFINED;
-  }
-
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    return TS_PROXY_UNDEFINED;
-  }
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, OpType::PROXY_STATE_GET, &err, &state);
-  ats_free(reply.ptr);
-
-  if (ret != TS_ERR_OKAY || err != TS_ERR_OKAY) {
-    return TS_PROXY_UNDEFINED;
-  }
-
-  return static_cast<TSProxyStateT>(state);
-}
-
-TSMgmtError
-ProxyStateSet(TSProxyStateT state, TSCacheClearT clear)
-{
-  TSMgmtError ret;
-  OpType optype          = OpType::PROXY_STATE_SET;
-  MgmtMarshallInt pstate = state;
-  MgmtMarshallInt pclear = clear;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::PROXY_STATE_SET, &optype, &pstate, &pclear);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::PROXY_STATE_SET, main_socket_fd) : ret;
-}
-
-TSMgmtError
-ServerBacktrace(unsigned options, char **trace)
-{
-  ink_release_assert(trace != nullptr);
-  TSMgmtError ret;
-  MgmtMarshallInt err;
-  OpType optype             = OpType::SERVER_BACKTRACE;
-  MgmtMarshallInt flags     = options;
-  MgmtMarshallData reply    = {nullptr, 0};
-  MgmtMarshallString strval = nullptr;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::SERVER_BACKTRACE, &optype, &flags);
-  if (ret != TS_ERR_OKAY) {
-    goto fail;
-  }
-
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    goto fail;
-  }
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, OpType::SERVER_BACKTRACE, &err, &strval);
-  if (ret != TS_ERR_OKAY) {
-    goto fail;
-  }
-
-  if (err != TS_ERR_OKAY) {
-    ret = static_cast<TSMgmtError>(err);
-    goto fail;
-  }
-
-  ats_free(reply.ptr);
-  *trace = strval;
-  return TS_ERR_OKAY;
-
-fail:
-  ats_free(reply.ptr);
-  ats_free(strval);
-  return ret;
-}
-
-TSMgmtError
-Reconfigure()
-{
-  TSMgmtError ret;
-  OpType optype = OpType::RECONFIGURE;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RECONFIGURE, &optype);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::RECONFIGURE, main_socket_fd) : ret;
-}
-
-/*-------------------------------------------------------------------------
- * Restart
- *-------------------------------------------------------------------------
- * if restart of TM is successful, need to reconnect to TM;
- * it's possible that the SUCCESS msg is received before the
- * restarting of TM is totally complete(?) b/c the core Restart call
- * only signals the event putting it in a msg queue;
- * so keep trying to reconnect until successful or for MAX_CONN_TRIES
- */
-TSMgmtError
-Restart(unsigned options)
-{
-  TSMgmtError ret;
-  OpType optype        = OpType::RESTART;
-  MgmtMarshallInt oval = options;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RESTART, &optype, &oval);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  ret = parse_generic_response(OpType::RESTART, main_socket_fd);
-  if (ret == TS_ERR_OKAY) {
-    ret = reconnect_loop(MAX_CONN_TRIES);
-  }
-
-  return ret;
-}
-
-/*-------------------------------------------------------------------------
- * Bounce
- *-------------------------------------------------------------------------
- * Restart the traffic_server process(es) only.
- */
-TSMgmtError
-Bounce(unsigned options)
-{
-  TSMgmtError ret;
-  OpType optype        = OpType::BOUNCE;
-  MgmtMarshallInt oval = options;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::BOUNCE, &optype, &oval);
-
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::BOUNCE, main_socket_fd) : ret;
-}
-
-/*-------------------------------------------------------------------------
- * Stop
- *-------------------------------------------------------------------------
- * Restart the traffic_server process(es) only.
- */
-TSMgmtError
-Stop(unsigned options)
-{
-  TSMgmtError ret;
-  OpType optype        = OpType::STOP;
-  MgmtMarshallInt oval = options;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::STOP, &optype, &oval);
-
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::STOP, main_socket_fd) : ret;
-}
-
-/*-------------------------------------------------------------------------
- * Drain
- *-------------------------------------------------------------------------
- * Drain requests of the traffic_server process(es) only.
- */
-TSMgmtError
-Drain(unsigned options)
-{
-  TSMgmtError ret;
-  OpType optype        = OpType::DRAIN;
-  MgmtMarshallInt oval = options;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::DRAIN, &optype, &oval);
-
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::DRAIN, main_socket_fd) : ret;
-}
-
-/*-------------------------------------------------------------------------
- * StorageDeviceCmdOffline
- *-------------------------------------------------------------------------
- * Disable a storage device.
- */
-TSMgmtError
-StorageDeviceCmdOffline(const char *dev)
-{
-  TSMgmtError ret;
-  OpType optype           = OpType::STORAGE_DEVICE_CMD_OFFLINE;
-  MgmtMarshallString name = const_cast<MgmtMarshallString>(dev);
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::STORAGE_DEVICE_CMD_OFFLINE, &optype, &name);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::STORAGE_DEVICE_CMD_OFFLINE, main_socket_fd) : ret;
-}
-
-/*-------------------------------------------------------------------------
- * Lifecycle Alert
- *-------------------------------------------------------------------------
- * Send alert to plugins
- */
-TSMgmtError
-LifecycleMessage(const char *tag, void const *data, size_t data_size)
-{
-  TSMgmtError ret;
-  OpType optype           = OpType::LIFECYCLE_MESSAGE;
-  MgmtMarshallString mtag = const_cast<MgmtMarshallString>(tag);
-  MgmtMarshallData mdata  = {const_cast<void *>(data), data_size};
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::LIFECYCLE_MESSAGE, &optype, &mtag, &mdata);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::LIFECYCLE_MESSAGE, main_socket_fd) : ret;
-}
-
-/***************************************************************************
- * Record Operations
- ***************************************************************************/
-
-static void
-mgmt_record_convert_value(TSRecordT rec_type, const MgmtMarshallData &data, TSRecordValueT &value)
-{
-  // convert the record value to appropriate type
-  if (data.ptr) {
-    switch (rec_type) {
-    case TS_REC_INT:
-      ink_assert(data.len == sizeof(TSInt));
-      value.int_val = *static_cast<TSInt *>(data.ptr);
-      break;
-    case TS_REC_COUNTER:
-      ink_assert(data.len == sizeof(TSCounter));
-      value.counter_val = *static_cast<TSCounter *>(data.ptr);
-      break;
-    case TS_REC_FLOAT:
-      ink_assert(data.len == sizeof(TSFloat));
-      value.float_val = *static_cast<TSFloat *>(data.ptr);
-      break;
-    case TS_REC_STRING:
-      ink_assert(data.len == strlen((char *)data.ptr) + 1);
-      value.string_val = ats_strdup((char *)data.ptr);
-      break;
-    default:; // nothing ... shut up compiler!
-    }
-  }
-}
-
-static TSMgmtError
-mgmt_record_get_reply(OpType op, TSRecordEle *rec_ele)
-{
-  TSMgmtError ret;
-
-  MgmtMarshallData reply = {nullptr, 0};
-  MgmtMarshallInt err;
-  MgmtMarshallInt rclass;
-  MgmtMarshallInt type;
-  MgmtMarshallString name = nullptr;
-  MgmtMarshallData value  = {nullptr, 0};
-
-  ink_zero(*rec_ele);
-  rec_ele->rec_type = TS_REC_UNDEFINED;
-
-  // Receive the next record reply.
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, op, &err, &rclass, &type, &name, &value);
-  ats_free(reply.ptr);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  if (err != TS_ERR_OKAY) {
-    ret = static_cast<TSMgmtError>(err);
-    goto done;
-  }
-
-  rec_ele->rec_class = static_cast<TSInt>(rclass);
-  rec_ele->rec_type  = static_cast<TSRecordT>(type);
-  rec_ele->rec_name  = ats_strdup(name);
-  mgmt_record_convert_value(rec_ele->rec_type, value, rec_ele->valueT);
-
-done:
-  ats_free(name);
-  ats_free(value.ptr);
-  return ret;
-}
-
-static TSMgmtError
-mgmt_record_describe_reply(TSConfigRecordDescription *val)
-{
-  TSMgmtError ret;
-  MgmtMarshallData reply = {nullptr, 0};
-
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  MgmtMarshallInt err;
-  MgmtMarshallString name = nullptr;
-  MgmtMarshallString expr = nullptr;
-  MgmtMarshallData value  = {nullptr, 0};
-  MgmtMarshallData deflt  = {nullptr, 0};
-
-  MgmtMarshallInt rtype;
-  MgmtMarshallInt rclass;
-  MgmtMarshallInt version;
-  MgmtMarshallInt rsb;
-  MgmtMarshallInt order;
-  MgmtMarshallInt access;
-  MgmtMarshallInt update;
-  MgmtMarshallInt updatetype;
-  MgmtMarshallInt checktype;
-  MgmtMarshallInt source;
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, OpType::RECORD_DESCRIBE_CONFIG, &err, &name, &value, &deflt, &rtype, &rclass,
-                           &version, &rsb, &order, &access, &update, &updatetype, &checktype, &source, &expr);
-
-  ats_free(reply.ptr);
-
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  if (err != TS_ERR_OKAY) {
-    ret = static_cast<TSMgmtError>(err);
-    goto done;
-  }
-
-  // Everything is cool, populate the description ...
-  val->rec_name       = ats_strdup(name);
-  val->rec_checkexpr  = ats_strdup(expr);
-  val->rec_type       = static_cast<TSRecordT>(rtype);
-  val->rec_class      = rclass;
-  val->rec_version    = version;
-  val->rec_rsb        = rsb;
-  val->rec_order      = order;
-  val->rec_access     = access;
-  val->rec_updatetype = updatetype;
-  val->rec_checktype  = checktype;
-  val->rec_source     = source;
-
-  mgmt_record_convert_value(val->rec_type, value, val->rec_value);
-  mgmt_record_convert_value(val->rec_type, deflt, val->rec_default);
-
-done:
-  ats_free(name);
-  ats_free(expr);
-  ats_free(value.ptr);
-  ats_free(deflt.ptr);
-  return ret;
-}
-
-// note that the record value is being sent as chunk of memory, regardless of
-// record type; it's not being converted to a string!!
-TSMgmtError
-MgmtRecordGet(const char *rec_name, TSRecordEle *rec_ele)
-{
-  TSMgmtError ret;
-  OpType optype             = OpType::RECORD_GET;
-  MgmtMarshallString record = const_cast<MgmtMarshallString>(rec_name);
-
-  if (!rec_name || !rec_ele) {
-    return TS_ERR_PARAMS;
-  }
-
-  // create and send request
-  if ((ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RECORD_GET, &optype, &record)) != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  // drop the response if the record name doesn't match
-  // we need to do this because there might be left over data on the socket
-  // when restarting traffic_server, even though it can't be recreated in a
-  // test environment, it has been observed in production the names doesn't
-  // match and caused traffic_cop to crash due to type mismatch.
-  while ((ret = mgmt_record_get_reply(OpType::RECORD_GET, rec_ele)) == TS_ERR_OKAY && strcmp(rec_name, rec_ele->rec_name) != 0) {
-  }
-  return ret;
-}
-
-TSMgmtError
-MgmtConfigRecordDescribeMatching(const char *rec_name, unsigned options, TSList rec_vals)
-{
-  TSMgmtError ret;
-  OpType optype             = OpType::RECORD_DESCRIBE_CONFIG;
-  MgmtMarshallInt flags     = options | RECORD_DESCRIBE_FLAGS_MATCH;
-  MgmtMarshallString record = const_cast<MgmtMarshallString>(rec_name);
-
-  // create and send request
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RECORD_DESCRIBE_CONFIG, &optype, &record, &flags);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  for (;;) {
-    TSConfigRecordDescription *val;
-
-    val = TSConfigRecordDescriptionCreate();
-
-    // parse the reply to get record value and type
-    ret = mgmt_record_describe_reply(val);
-    if (ret != TS_ERR_OKAY) {
-      TSConfigRecordDescriptionDestroy(val);
-      goto fail;
-    }
-
-    // A NULL record ends the list.
-    if (val->rec_type == TS_REC_UNDEFINED) {
-      TSConfigRecordDescriptionDestroy(val);
-      break;
-    }
-
-    enqueue(static_cast<LLQ *>(rec_vals), val);
-  }
-
-  return TS_ERR_OKAY;
-
-fail:
-  while (!queue_is_empty(static_cast<LLQ *>(rec_vals))) {
-    TSConfigRecordDescription *val = static_cast<TSConfigRecordDescription *>(dequeue(static_cast<LLQ *>(rec_vals)));
-    TSConfigRecordDescriptionDestroy(val);
-  }
-
-  return ret;
-}
-
-TSMgmtError
-MgmtConfigRecordDescribe(const char *rec_name, unsigned options, TSConfigRecordDescription *val)
-{
-  TSMgmtError ret;
-  OpType optype             = OpType::RECORD_DESCRIBE_CONFIG;
-  MgmtMarshallInt flags     = options & ~RECORD_DESCRIBE_FLAGS_MATCH;
-  MgmtMarshallString record = const_cast<MgmtMarshallString>(rec_name);
-
-  // create and send request
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RECORD_DESCRIBE_CONFIG, &optype, &record, &flags);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  return mgmt_record_describe_reply(val);
-}
-
-TSMgmtError
-MgmtRecordGetMatching(const char *regex, TSList rec_vals)
-{
-  TSMgmtError ret;
-  TSRecordEle *rec_ele;
-
-  OpType optype             = OpType::RECORD_MATCH_GET;
-  MgmtMarshallString record = const_cast<MgmtMarshallString>(regex);
-
-  if (!regex || !rec_vals) {
-    return TS_ERR_PARAMS;
-  }
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::RECORD_MATCH_GET, &optype, &record);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  for (;;) {
-    rec_ele = TSRecordEleCreate();
-
-    // parse the reply to get record value and type
-    ret = mgmt_record_get_reply(OpType::RECORD_MATCH_GET, rec_ele);
-    if (ret != TS_ERR_OKAY) {
-      TSRecordEleDestroy(rec_ele);
-      goto fail;
-    }
-
-    // A NULL record ends the list.
-    if (rec_ele->rec_type == TS_REC_UNDEFINED) {
-      TSRecordEleDestroy(rec_ele);
-      break;
-    }
-
-    enqueue(static_cast<LLQ *>(rec_vals), rec_ele);
-  }
-
-  return TS_ERR_OKAY;
-
-fail:
-  while (!queue_is_empty(static_cast<LLQ *>(rec_vals))) {
-    rec_ele = static_cast<TSRecordEle *>(dequeue(static_cast<LLQ *>(rec_vals)));
-    TSRecordEleDestroy(rec_ele);
-  }
-
-  return ret;
-}
-
-TSMgmtError
-MgmtRecordSet(const char *rec_name, const char *val, TSActionNeedT *action_need)
-{
-  TSMgmtError ret;
-
-  if (!rec_name || !val || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  ret = mgmt_record_set(rec_name, val, action_need);
-  return ret;
-}
-
-// first convert the MgmtInt into a string
-// NOTE: use long long, not just long, MgmtInt = int64_t
-TSMgmtError
-MgmtRecordSetInt(const char *rec_name, MgmtInt int_val, TSActionNeedT *action_need)
-{
-  char str_val[MAX_RECORD_SIZE];
-  TSMgmtError ret;
-
-  if (!rec_name || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  bzero(str_val, MAX_RECORD_SIZE);
-  snprintf(str_val, sizeof(str_val), "%" PRId64 "", int_val);
-  ret = mgmt_record_set(rec_name, str_val, action_need);
-
-  return ret;
-}
-
-// first convert the MgmtIntCounter into a string
-TSMgmtError
-MgmtRecordSetCounter(const char *rec_name, MgmtIntCounter counter_val, TSActionNeedT *action_need)
-{
-  char str_val[MAX_RECORD_SIZE];
-  TSMgmtError ret;
-
-  if (!rec_name || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  bzero(str_val, MAX_RECORD_SIZE);
-  snprintf(str_val, sizeof(str_val), "%" PRId64 "", counter_val);
-  ret = mgmt_record_set(rec_name, str_val, action_need);
-
-  return ret;
-}
-
-// first convert the MgmtFloat into string
-TSMgmtError
-MgmtRecordSetFloat(const char *rec_name, MgmtFloat float_val, TSActionNeedT *action_need)
-{
-  char str_val[MAX_RECORD_SIZE];
-  TSMgmtError ret;
-
-  bzero(str_val, MAX_RECORD_SIZE);
-  if (snprintf(str_val, sizeof(str_val), "%f", float_val) < 0) {
-    return TS_ERR_SYS_CALL;
-  }
-  ret = mgmt_record_set(rec_name, str_val, action_need);
-
-  return ret;
-}
-
-TSMgmtError
-MgmtRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT *action_need)
-{
-  TSMgmtError ret;
-
-  if (!rec_name || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  ret = mgmt_record_set(rec_name, string_val, action_need);
-  return ret;
-}
-
-/***************************************************************************
- * Events
- ***************************************************************************/
-/*-------------------------------------------------------------------------
- * EventSignal
- *-------------------------------------------------------------------------
- * LAN - need to implement
- */
-TSMgmtError
-EventSignal(const char * /* event_name ATS_UNUSED */, va_list /* ap ATS_UNUSED */)
-{
-  return TS_ERR_FAIL;
-}
-
-/*-------------------------------------------------------------------------
- * EventResolve
- *-------------------------------------------------------------------------
- * purpose: Resolves the event of the specified name
- * note:    when sending the message request, actually sends the event name,
- *          not the event id
- */
-TSMgmtError
-EventResolve(const char *event_name)
-{
-  TSMgmtError ret;
-  OpType optype           = OpType::EVENT_RESOLVE;
-  MgmtMarshallString name = const_cast<MgmtMarshallString>(event_name);
-
-  if (!event_name) {
-    return TS_ERR_PARAMS;
-  }
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::EVENT_RESOLVE, &optype, &name);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(OpType::EVENT_RESOLVE, main_socket_fd) : ret;
-}
-
-/*-------------------------------------------------------------------------
- * ActiveEventGetMlt
- *-------------------------------------------------------------------------
- * purpose: Retrieves a list of active(unresolved) events
- * note:    list of event names returned in network msg which must be tokenized
- */
-TSMgmtError
-ActiveEventGetMlt(LLQ *active_events)
-{
-  if (!active_events) {
-    return TS_ERR_PARAMS;
-  }
-
-  return (send_and_parse_list(OpType::EVENT_GET_MLT, active_events));
-}
-
-/*-------------------------------------------------------------------------
- * EventIsActive
- *-------------------------------------------------------------------------
- * determines if the event_name is active; sets result in is_current
- */
-TSMgmtError
-EventIsActive(const char *event_name, bool *is_current)
-{
-  TSMgmtError ret;
-  OpType optype           = OpType::EVENT_ACTIVE;
-  MgmtMarshallString name = const_cast<MgmtMarshallString>(event_name);
-
-  MgmtMarshallData reply = {nullptr, 0};
-  MgmtMarshallInt err;
-  MgmtMarshallInt bval;
-
-  if (!event_name || !is_current) {
-    return TS_ERR_PARAMS;
-  }
-
-  // create and send request
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::EVENT_ACTIVE, &optype, &name);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  ret = recv_mgmt_message(main_socket_fd, reply);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  ret = recv_mgmt_response(reply.ptr, reply.len, OpType::EVENT_ACTIVE, &err, &bval);
-  ats_free(reply.ptr);
-
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  *is_current = (bval != 0);
-  return static_cast<TSMgmtError>(err);
-}
-
-/*-------------------------------------------------------------------------
- * EventSignalCbRegister
- *-------------------------------------------------------------------------
- * Adds the callback function in appropriate places in the remote side
- * callback table.
- * If this is the first callback to be registered for a certain event type,
- * then sends a callback registration notification to TM so that TM will know
- * which events have remote callbacks registered on it.
- */
-TSMgmtError
-EventSignalCbRegister(const char *event_name, TSEventSignalFunc func, void *data)
-{
-  bool first_time = false;
-  TSMgmtError ret;
-
-  if (func == nullptr) {
-    return TS_ERR_PARAMS;
-  }
-  if (!remote_event_callbacks) {
-    return TS_ERR_FAIL;
-  }
-
-  ret = cb_table_register(remote_event_callbacks, event_name, func, data, &first_time);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  // if we need to notify traffic manager of the event then send msg
-  if (first_time) {
-    OpType optype           = OpType::EVENT_REG_CALLBACK;
-    MgmtMarshallString name = const_cast<MgmtMarshallString>(event_name);
-
-    ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, OpType::EVENT_REG_CALLBACK, &optype, &name);
-    if (ret != TS_ERR_OKAY) {
-      return ret;
-    }
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/*-------------------------------------------------------------------------
- * EventSignalCbUnregister
- *-------------------------------------------------------------------------
- * Removes the callback function from the remote side callback table.
- * After removing the callback function, needs to check which events now
- * no longer have any callbacks registered at all; sends an unregister callback
- * notification to TM so that TM knows that event doesn't have any
- * remote callbacks registered for it
- * Input: event_name - the event to unregister the callback from; if NULL,
- *                     unregisters the specified func from all events
- *        func       - the callback function to unregister; if NULL, then
- *                     unregisters all callback functions for the event_name
- *                     specified
- */
-TSMgmtError
-EventSignalCbUnregister(const char *event_name, TSEventSignalFunc func)
-{
-  TSMgmtError err;
-
-  if (!remote_event_callbacks) {
-    return TS_ERR_FAIL;
-  }
-
-  // remove the callback function from the table
-  err = cb_table_unregister(remote_event_callbacks, event_name, func);
-  if (err != TS_ERR_OKAY) {
-    return err;
-  }
-
-  // check if we need to notify traffic manager of the event (notify TM
-  // only if the event has no callbacks)
-  err = send_unregister_all_callbacks(event_socket_fd, remote_event_callbacks);
-  if (err != TS_ERR_OKAY) {
-    return err;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-TSMgmtError
-HostStatusSetDown(const char *host_name, int down_time, const char *reason)
-{
-  TSMgmtError ret         = TS_ERR_PARAMS;
-  OpType op               = OpType::HOST_STATUS_DOWN;
-  MgmtMarshallString name = const_cast<MgmtMarshallString>(host_name);
-  MgmtMarshallString re   = const_cast<MgmtMarshallString>(reason);
-  MgmtMarshallInt dtime   = down_time;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, op, &op, &name, &re, &dtime);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(op, main_socket_fd) : ret;
-}
-
-TSMgmtError
-HostStatusSetUp(const char *host_name, int down_time, const char *reason)
-{
-  TSMgmtError ret         = TS_ERR_PARAMS;
-  OpType op               = OpType::HOST_STATUS_UP;
-  MgmtMarshallString name = const_cast<MgmtMarshallString>(host_name);
-  MgmtMarshallString re   = const_cast<MgmtMarshallString>(reason);
-  MgmtMarshallInt dtime   = down_time;
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, op, &op, &name, &re, &dtime);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(op, main_socket_fd) : ret;
-}
-
-TSMgmtError
-StatsReset(const char *stat_name)
-{
-  TSMgmtError ret;
-  OpType op               = OpType::STATS_RESET_NODE;
-  OpType optype           = op;
-  MgmtMarshallString name = const_cast<MgmtMarshallString>(stat_name);
-
-  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, op, &optype, &name);
-  return (ret == TS_ERR_OKAY) ? parse_generic_response(op, main_socket_fd) : ret;
-}
diff --git a/mgmt/api/CoreAPIShared.cc b/mgmt/api/CoreAPIShared.cc
index dc580ac..5514afe 100644
--- a/mgmt/api/CoreAPIShared.cc
+++ b/mgmt/api/CoreAPIShared.cc
@@ -27,7 +27,6 @@
 #include "tscore/ink_memory.h"
 
 #include "CoreAPIShared.h"
-#include "MgmtSocket.h"
 
 // Forward declarations, used to be in the CoreAPIShared.h include file but
 // that doesn't make any sense since these are both statically declared. /leif
@@ -278,87 +277,3 @@
 
   return err;
 }
-
-/**********************************************************************
- * Events
- **********************************************************************/
-/**********************************************************************
- * get_event_id
- *
- * Purpose: Given the event_name, returns the event's corresponding
- *          event id
- * Note: this conversion is based on list defined in Alarms.h and
- *       the identical list defined in CoreAPIShared.cc
- *********************************************************************/
-int
-get_event_id(const char *event_name)
-{
-  if (strcmp("MGMT_ALARM_PROXY_PROCESS_DIED", event_name) == 0) {
-    return MGMT_ALARM_PROXY_PROCESS_DIED;
-  } else if (strcmp("MGMT_ALARM_PROXY_PROCESS_BORN", event_name) == 0) {
-    return MGMT_ALARM_PROXY_PROCESS_BORN;
-  } else if (strcmp("MGMT_ALARM_PROXY_CONFIG_ERROR", event_name) == 0) {
-    return MGMT_ALARM_PROXY_CONFIG_ERROR;
-  } else if (strcmp("MGMT_ALARM_PROXY_SYSTEM_ERROR", event_name) == 0) {
-    return MGMT_ALARM_PROXY_SYSTEM_ERROR;
-  } else if (strcmp("MGMT_ALARM_PROXY_CACHE_ERROR", event_name) == 0) {
-    return MGMT_ALARM_PROXY_CACHE_ERROR;
-  } else if (strcmp("MGMT_ALARM_PROXY_CACHE_WARNING", event_name) == 0) {
-    return MGMT_ALARM_PROXY_CACHE_WARNING;
-  } else if (strcmp("MGMT_ALARM_PROXY_LOGGING_ERROR", event_name) == 0) {
-    return MGMT_ALARM_PROXY_LOGGING_ERROR;
-  } else if (strcmp("MGMT_ALARM_PROXY_LOGGING_WARNING", event_name) == 0) {
-    return MGMT_ALARM_PROXY_LOGGING_WARNING;
-  } else if (strcmp("MGMT_ALARM_CONFIG_UPDATE_FAILED", event_name) == 0) {
-    return MGMT_ALARM_CONFIG_UPDATE_FAILED;
-  }
-
-  return -1;
-}
-
-/**********************************************************************
- * get_event_id
- *
- * Purpose: based on alarm_id, determine the corresponding alarm name
- * Note:    allocates memory for the name returned
- *********************************************************************/
-char *
-get_event_name(int id)
-{
-  char name[MAX_EVENT_NAME_SIZE];
-
-  memset(name, 0, MAX_EVENT_NAME_SIZE);
-  switch (id) {
-  case MGMT_ALARM_PROXY_PROCESS_DIED:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_PROCESS_DIED", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_PROCESS_BORN:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_PROCESS_BORN", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_CONFIG_ERROR:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_CONFIG_ERROR", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_SYSTEM_ERROR:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_SYSTEM_ERROR", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_CACHE_ERROR:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_CACHE_ERROR", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_CACHE_WARNING:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_CACHE_WARNING", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_LOGGING_ERROR:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_LOGGING_ERROR", sizeof(name));
-    break;
-  case MGMT_ALARM_PROXY_LOGGING_WARNING:
-    ink_strlcpy(name, "MGMT_ALARM_PROXY_LOGGING_WARNING", sizeof(name));
-    break;
-  case MGMT_ALARM_CONFIG_UPDATE_FAILED:
-    ink_strlcpy(name, "MGMT_ALARM_CONFIG_UPDATE_FAILED", sizeof(name));
-    break;
-  default:
-    return nullptr;
-  }
-
-  return ats_strdup(name);
-}
diff --git a/mgmt/api/CoreAPIShared.h b/mgmt/api/CoreAPIShared.h
index 6718f8a..6e1e8d7 100644
--- a/mgmt/api/CoreAPIShared.h
+++ b/mgmt/api/CoreAPIShared.h
@@ -34,27 +34,6 @@
 
 #include "mgmtapi.h"
 
-#define NUM_EVENTS 19           // number of predefined TM events
-#define MAX_EVENT_NAME_SIZE 100 // max length for an event name
-#define MAX_RECORD_SIZE 20      // max length of buffer to hold record values
-
-// LAN - BAD BHACK; copied from Alarms.h !!!!!
-/* Must be same as defined in Alarms.h; the reason we had to
- * redefine them here is because the remote client also needs
- * access to these values for its event handling
- */
-#define MGMT_ALARM_UNDEFINED 0
-
-#define MGMT_ALARM_PROXY_PROCESS_DIED 1
-#define MGMT_ALARM_PROXY_PROCESS_BORN 2
-#define MGMT_ALARM_PROXY_CONFIG_ERROR 3
-#define MGMT_ALARM_PROXY_SYSTEM_ERROR 4
-#define MGMT_ALARM_PROXY_CACHE_ERROR 5
-#define MGMT_ALARM_PROXY_CACHE_WARNING 6
-#define MGMT_ALARM_PROXY_LOGGING_ERROR 7
-#define MGMT_ALARM_PROXY_LOGGING_WARNING 8
-#define MGMT_ALARM_CONFIG_UPDATE_FAILED 9
-
 // used by TSReadFromUrl
 #define HTTP_DIVIDER "\r\n\r\n"
 #define URL_BUFSIZE 65536 // the max. length of URL obtainable (in bytes)
@@ -62,15 +41,8 @@
 #define HTTP_PORT 80
 #define BUFSIZE 1024
 
-// Flags for management API behaviour.
-#define MGMT_API_PRIVILEGED 0x0001u
-
 // used by TSReadFromUrl
 TSMgmtError parseHTTPResponse(char *buffer, char **header, int *hdr_size, char **body, int *bdy_size);
 TSMgmtError readHTTPResponse(int sock, char *buffer, int bufsize, uint64_t timeout);
 TSMgmtError sendHTTPRequest(int sock, char *request, uint64_t timeout);
 int connectDirect(const char *host, int port, uint64_t timeout);
-
-// used for Events
-int get_event_id(const char *event_name);
-char *get_event_name(int id);
diff --git a/mgmt/api/EventCallback.cc b/mgmt/api/EventCallback.cc
deleted file mode 100644
index a0a7076..0000000
--- a/mgmt/api/EventCallback.cc
+++ /dev/null
@@ -1,348 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: EventCallback.cc
- * Purpose: Generic module that deals with callbacks and a callback table
- * Created: 01/08/01
- * Created by: lant
- *
- ***************************************************************************/
-
-#include "tscore/ink_config.h"
-#include "tscore/ink_memory.h"
-
-#include "EventCallback.h"
-#include "CoreAPIShared.h"
-
-/**********************************************************************
- * create_event_callback
- *
- * purpose: allocates and initializes members of EventCallbackT
- * input: None
- * output: EventCallbackT
- * notes: None
- **********************************************************************/
-EventCallbackT *
-create_event_callback(TSEventSignalFunc func, void *data)
-{
-  EventCallbackT *event_cb = static_cast<EventCallbackT *>(ats_malloc(sizeof(EventCallbackT)));
-
-  event_cb->func = func;
-  event_cb->data = data;
-
-  return event_cb;
-}
-
-/**********************************************************************
- * delete_event_callback
- *
- * purpose:frees EventCallbackT
- * input: None
- * output: EventCallbackT
- * notes: also frees memory for the data passed in; ASSUMES data was
- *        dynamically allocated
- **********************************************************************/
-void
-delete_event_callback(EventCallbackT *event_cb)
-{
-  ats_free(event_cb);
-  return;
-}
-
-/**********************************************************************
- * create_callback_table
- *
- * purpose: initializes the structures used to deal with events
- * input: None
- * output: TS_ERR_xx
- * notes: None
- **********************************************************************/
-CallbackTable *
-create_callback_table(const char *)
-{
-  CallbackTable *cb_table = static_cast<CallbackTable *>(ats_malloc(sizeof(CallbackTable)));
-
-  for (auto &i : cb_table->event_callback_l) {
-    i = nullptr;
-  }
-
-  // initialize the mutex
-  ink_mutex_init(&cb_table->event_callback_lock);
-  return cb_table;
-}
-
-/**********************************************************************
- * delete_callback_table
- *
- * purpose: frees the memory allocated for a CallbackTable; also
- *          destroys the lock
- * input: None
- * output: None
- * notes: doesn't free pointers to functions
- **********************************************************************/
-void
-delete_callback_table(CallbackTable *cb_table)
-{
-  EventCallbackT *event_cb;
-
-  // get lock
-  ink_mutex_acquire(&cb_table->event_callback_lock);
-
-  // for each event
-  for (auto &i : cb_table->event_callback_l) {
-    if (i) {
-      // remove and delete each EventCallbackT for that event
-      while (!queue_is_empty(i)) {
-        event_cb = static_cast<EventCallbackT *>(dequeue(i));
-        delete_event_callback(event_cb);
-      }
-
-      delete_queue(i);
-    }
-  }
-
-  // release lock
-  ink_mutex_release(&cb_table->event_callback_lock);
-
-  // destroy lock
-  ink_mutex_destroy(&cb_table->event_callback_lock);
-
-  ats_free(cb_table);
-
-  return;
-}
-
-/**********************************************************************
- * get_events_with_callbacks
- *
- * purpose:  returns a list of the event_id's that have at least
- *           one callback registered for that event
- * input: cb_list - the table of callbacks to check
- * output: returns a list of event_ids with at least one callback fun;
- *         returns NULL if all the events have a registered callback
- * notes:
- **********************************************************************/
-LLQ *
-get_events_with_callbacks(CallbackTable *cb_table)
-{
-  LLQ *cb_ev_list;
-  bool all_events = true; // set to false if at least one event doesn't have registered callback
-
-  cb_ev_list = create_queue();
-  for (int i = 0; i < NUM_EVENTS; i++) {
-    if (!cb_table->event_callback_l[i]) {
-      all_events = false;
-      continue; // no callbacks registered
-    }
-
-    enqueue(cb_ev_list, &i);
-  }
-
-  if (all_events) {
-    delete_queue(cb_ev_list);
-    return nullptr;
-  }
-
-  return cb_ev_list;
-}
-
-/**********************************************************************
- * cb_table_register
- *
- * purpose: Registers the specified function for the specified event in
- *          the specified callback list
- * input: cb_list - the table of callbacks to store the callback fn
- *        event_name - the event to store the callback for (if NULL, register for all events)
- *        func - the callback function
- *        first_cb - true only if this is the event's first callback
- * output: TS_ERR_xx
- * notes:
- **********************************************************************/
-TSMgmtError
-cb_table_register(CallbackTable *cb_table, const char *event_name, TSEventSignalFunc func, void *data, bool *first_cb)
-{
-  bool first_time = false;
-  EventCallbackT *event_cb; // create new EventCallbackT EACH TIME enqueue
-
-  // the data and event_name can be NULL
-  if (func == nullptr || !cb_table) {
-    return TS_ERR_PARAMS;
-  }
-
-  ink_mutex_acquire(&(cb_table->event_callback_lock));
-
-  // got lock, add it
-  if (event_name == nullptr) { // register for all alarms
-    // printf("[EventSignalCbRegister] Register callback for all alarms\n");
-    for (auto &i : cb_table->event_callback_l) {
-      if (!i) {
-        i          = create_queue();
-        first_time = true;
-      }
-
-      if (!i) {
-        ink_mutex_release(&cb_table->event_callback_lock);
-        return TS_ERR_SYS_CALL;
-      }
-
-      event_cb = create_event_callback(func, data);
-      enqueue(i, event_cb);
-    }
-  } else { // register callback for specific alarm
-    int id = get_event_id(event_name);
-    if (id != -1) {
-      if (!cb_table->event_callback_l[id]) {
-        cb_table->event_callback_l[id] = create_queue();
-        first_time                     = true;
-      }
-
-      if (!cb_table->event_callback_l[id]) {
-        ink_mutex_release(&cb_table->event_callback_lock);
-        return TS_ERR_SYS_CALL;
-      }
-      // now add to list
-      event_cb = create_event_callback(func, data);
-      enqueue(cb_table->event_callback_l[id], event_cb);
-    }
-  }
-
-  // release lock on callback table
-  ink_mutex_release(&cb_table->event_callback_lock);
-
-  if (first_cb) {
-    *first_cb = first_time;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/**********************************************************************
- * cb_table_unregister
- *
- * purpose: Unregisters the specified function for the specified event in
- *          the specified callback list
- * input: cb_table - the table of callbacks to store the callback fn
- *        event_name - the event to store the callback for (if NULL, register for all events)
- *        func - the callback function
- *        first_cb - true only if this is the event's first callback
- * output: TS_ERR_xx
- * notes:
- **********************************************************************/
-TSMgmtError
-cb_table_unregister(CallbackTable *cb_table, const char *event_name, TSEventSignalFunc func)
-{
-  TSEventSignalFunc cb_fun;
-  EventCallbackT *event_cb;
-
-  ink_mutex_acquire(&cb_table->event_callback_lock);
-
-  // got lock, add it
-  if (event_name == nullptr) { // unregister the callback for ALL EVENTS
-    // for each event
-    for (auto &i : cb_table->event_callback_l) {
-      if (!i) { // this event has no callbacks
-        continue;
-      }
-
-      // func == NULL means unregister all functions associated with alarm
-      if (func == nullptr) {
-        while (!queue_is_empty(i)) {
-          event_cb = static_cast<EventCallbackT *>(dequeue(i));
-          delete_event_callback(event_cb);
-        }
-        // clean up queue and set to NULL
-        delete_queue(i);
-        i = nullptr;
-      } else { // only remove the func passed in
-        int queue_depth;
-
-        queue_depth = queue_len(i);
-        // remove this function
-        for (int j = 0; j < queue_depth; j++) {
-          event_cb = static_cast<EventCallbackT *>(dequeue(i));
-          cb_fun   = event_cb->func;
-
-          // the pointers are the same so don't enqueue the fn back on
-          if (*cb_fun == *func) {
-            delete_event_callback(event_cb);
-            continue;
-          }
-
-          enqueue(i, event_cb);
-        }
-
-        // is queue empty now? then clean up
-        if (queue_is_empty(i)) {
-          delete_queue(i);
-          i = nullptr;
-        }
-      }
-    } // end for (int i = 0; i < NUM_EVENTS; i++)
-  } else {
-    // unregister for specific event
-    int id = get_event_id(event_name);
-    if (id != -1) {
-      if (cb_table->event_callback_l[id]) {
-        int queue_depth;
-
-        queue_depth = queue_len(cb_table->event_callback_l[id]);
-        // func == NULL means unregister all functions associated with alarm
-        if (func == nullptr) {
-          while (!queue_is_empty(cb_table->event_callback_l[id])) {
-            event_cb = static_cast<EventCallbackT *>(dequeue(cb_table->event_callback_l[id]));
-            delete_event_callback(event_cb);
-          }
-
-          // clean up queue and set to NULL
-          delete_queue(cb_table->event_callback_l[id]);
-          cb_table->event_callback_l[id] = nullptr;
-        } else {
-          // remove this function
-          for (int j = 0; j < queue_depth; j++) {
-            event_cb = static_cast<EventCallbackT *>(dequeue(cb_table->event_callback_l[id]));
-            cb_fun   = event_cb->func;
-
-            // the pointers are the same
-            if (*cb_fun == *func) {
-              delete_event_callback(event_cb);
-              continue;
-            }
-
-            enqueue(cb_table->event_callback_l[id], event_cb);
-          }
-
-          // is queue empty now?
-          if (queue_is_empty(cb_table->event_callback_l[id])) {
-            delete_queue(cb_table->event_callback_l[id]);
-            cb_table->event_callback_l[id] = nullptr;
-          }
-        } // end if NULL else
-      }
-    }
-  }
-
-  ink_mutex_release(&cb_table->event_callback_lock);
-
-  return TS_ERR_OKAY;
-}
diff --git a/mgmt/api/EventCallback.h b/mgmt/api/EventCallback.h
deleted file mode 100644
index 34743f0..0000000
--- a/mgmt/api/EventCallback.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/***************************************************************************
- * EventCallback.h
- * Purpose:  defines the CallbackTable which stores callback functions for
- *           specific events
- *
- *
- ***************************************************************************/
-/*
- * EventCallback.h
- *
- * defines the CallbackTable which stores callback functions for
- * specific events. Used by both local api and remote api.
- */
-
-#pragma once
-
-#include "tscore/ink_llqueue.h"
-
-#include "mgmtapi.h"
-#include "CoreAPIShared.h"
-
-// when registering the callback function, can pass in void* data which
-// will then be passed to the callback function; need to store this data with
-// the callback in a struct
-typedef struct {
-  TSEventSignalFunc func;
-  void *data;
-} EventCallbackT;
-
-// event_call_back_l is a queue of EventCallbackT
-typedef struct {
-  LLQ *event_callback_l[NUM_EVENTS];
-  ink_mutex event_callback_lock;
-} CallbackTable;
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-EventCallbackT *create_event_callback(TSEventSignalFunc func, void *data);
-void delete_event_callback(EventCallbackT *event_cb);
-
-CallbackTable *create_callback_table(const char *lock_name);
-
-void delete_callback_table(CallbackTable *cb_table);
-
-// returns list of event_id that have at least one callback registered for it
-LLQ *get_events_with_callbacks(CallbackTable *cb_table);
-
-TSMgmtError cb_table_register(CallbackTable *cb_table, const char *event_name, TSEventSignalFunc func, void *data, bool *first_cb);
-TSMgmtError cb_table_unregister(CallbackTable *cb_table, const char *event_name, TSEventSignalFunc func);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
diff --git a/mgmt/api/EventControlMain.cc b/mgmt/api/EventControlMain.cc
deleted file mode 100644
index ff154cb..0000000
--- a/mgmt/api/EventControlMain.cc
+++ /dev/null
@@ -1,549 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: EventControlMain.cc
- * Purpose: Handles all event requests from the user.
- * Created: 01/08/01
- * Created by: lant
- *
- ***************************************************************************/
-
-#include "tscore/ink_platform.h"
-#include "tscore/ink_sock.h"
-#include "LocalManager.h"
-#include "MgmtSocket.h"
-#include "MgmtMarshall.h"
-#include "MgmtUtils.h"
-#include "EventControlMain.h"
-#include "CoreAPI.h"
-#include "NetworkUtilsLocal.h"
-#include "NetworkMessage.h"
-
-// variables that are very important
-ink_mutex mgmt_events_lock;
-LLQ *mgmt_events;
-std::unordered_map<int, EventClientT *> accepted_clients; // list of all accepted client connections
-
-static TSMgmtError handle_event_message(EventClientT *client, void *req, size_t reqlen);
-
-/*********************************************************************
- * new_event_client
- *
- * purpose: creates a new EventClientT and return pointer to it
- * input: None
- * output: EventClientT
- * note: None
- *********************************************************************/
-EventClientT *
-new_event_client()
-{
-  EventClientT *ele = static_cast<EventClientT *>(ats_malloc(sizeof(EventClientT)));
-
-  // now set the alarms registered section
-  for (bool &i : ele->events_registered) {
-    i = false;
-  }
-
-  ele->adr = static_cast<struct sockaddr *>(ats_malloc(sizeof(struct sockaddr)));
-  return ele;
-}
-
-/*********************************************************************
- * delete_event_client
- *
- * purpose: frees memory allocated for an EventClientT
- * input: EventClientT
- * output: None
- * note: None
- *********************************************************************/
-void
-delete_event_client(EventClientT *client)
-{
-  if (client) {
-    ats_free(client->adr);
-    ats_free(client);
-  }
-  return;
-}
-
-/*********************************************************************
- * remove_event_client
- *
- * purpose: removes the EventClientT from the specified hashtable; includes
- *          removing the binding and freeing the ClientT
- * input: client - the ClientT to remove
- * output:
- *********************************************************************/
-void
-remove_event_client(EventClientT *client, std::unordered_map<int, EventClientT *> &table)
-{
-  // close client socket
-  close_socket(client->fd);
-
-  // remove client binding from hash table
-  table.erase(client->fd);
-
-  // free ClientT
-  delete_event_client(client);
-
-  return;
-}
-
-/*********************************************************************
- * init_mgmt_events
- *
- * purpose: initializes the mgmt_events queue which is intended to hold
- *          TM events.
- * input:
- * output: TS_ERR_xx
- * note: None
- *********************************************************************/
-TSMgmtError
-init_mgmt_events()
-{
-  ink_mutex_init(&mgmt_events_lock);
-
-  // initialize queue
-  mgmt_events = create_queue();
-  if (!mgmt_events) {
-    ink_mutex_destroy(&mgmt_events_lock);
-    return TS_ERR_SYS_CALL;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/*********************************************************************
- * delete_mgmt_events
- *
- * purpose: frees the mgmt_events queue.
- * input:
- * output: None
- * note: None
- *********************************************************************/
-void
-delete_mgmt_events()
-{
-  // obtain lock
-  ink_mutex_acquire(&mgmt_events_lock);
-
-  // delete the queue associated with the queue of events
-  delete_event_queue(mgmt_events);
-
-  // release it
-  ink_mutex_release(&mgmt_events_lock);
-
-  // kill lock
-  ink_mutex_destroy(&mgmt_events_lock);
-
-  delete_queue(mgmt_events);
-
-  return;
-}
-
-/*********************************************************************
- * delete_event_queue
- *
- * purpose: frees queue where the elements are of type TSMgmtEvent* 's
- * input: LLQ * q - a queue with entries of TSMgmtEvent*'s
- * output: None
- * note: None
- *********************************************************************/
-void
-delete_event_queue(LLQ *q)
-{
-  if (!q) {
-    return;
-  }
-
-  while (!queue_is_empty(q)) {
-    TSMgmtEvent *ele = static_cast<TSMgmtEvent *>(dequeue(q));
-    ats_free(ele);
-  }
-
-  delete_queue(q);
-  return;
-}
-
-/*********************************************************************
- * apiEventCallback
- *
- * purpose: callback function registered with alarm processor so that
- *          each time alarm is signalled, can enqueue it in the mgmt_events
- *          queue
- * input:
- * output: None
- * note: None
- *********************************************************************/
-void
-apiEventCallback(alarm_t newAlarm, const char * /* ip ATS_UNUSED */, const char *desc)
-{
-  // create an TSMgmtEvent
-  // addEvent(new_alarm, ip, desc) // adds event to mgmt_events
-  TSMgmtEvent *newEvent;
-
-  newEvent       = TSEventCreate();
-  newEvent->id   = newAlarm;
-  newEvent->name = get_event_name(newEvent->id);
-  // newEvent->ip   = ats_strdup(ip);
-  if (desc) {
-    newEvent->description = ats_strdup(desc);
-  } else {
-    newEvent->description = ats_strdup("None");
-  }
-
-  // add it to the mgmt_events list
-  ink_mutex_acquire(&mgmt_events_lock);
-  enqueue(mgmt_events, newEvent);
-  ink_mutex_release(&mgmt_events_lock);
-
-  return;
-}
-
-/*********************************************************************
- * event_callback_main
- *
- * This function is run as a thread in WebIntrMain.cc that listens on a
- * specified socket. It loops until Traffic Manager dies.
- * In the loop, it just listens on a socket, ready to accept any connections,
- * until receives a request from the remote API client. Parse the request
- * to determine which CoreAPI call to make.
- *********************************************************************/
-void *
-event_callback_main(void *arg)
-{
-  int ret;
-  int *socket_fd;
-  int con_socket_fd; // main socket for listening to new connections
-
-  socket_fd     = static_cast<int *>(arg);
-  con_socket_fd = *socket_fd; // the socket for event callbacks
-
-  Debug("event", "[event_callback_main] listen on socket = %d", con_socket_fd);
-
-  // initialize queue for holding mgmt events
-  if (init_mgmt_events() != TS_ERR_OKAY) {
-    return nullptr;
-  }
-  // register callback with alarms processor
-  lmgmt->alarm_keeper->registerCallback(apiEventCallback);
-
-  // now we can start listening, accepting connections and servicing requests
-  int new_con_fd; // new connection fd when socket accepts connection
-
-  fd_set selectFDs;           // for select call
-  EventClientT *client_entry; // an entry of fd to alarms mapping
-  struct timeval timeout;
-
-  while (true) {
-    // LINUX fix: to prevent hard-spin reset timeout on each loop
-    timeout.tv_sec  = 1;
-    timeout.tv_usec = 0;
-
-    FD_ZERO(&selectFDs);
-
-    if (con_socket_fd >= 0) {
-      FD_SET(con_socket_fd, &selectFDs);
-      Debug("event", "[event_callback_main] add fd %d to select set", con_socket_fd);
-    }
-    // see if there are more fd to set
-    for (auto &&it : accepted_clients) {
-      client_entry = it.second;
-      if (client_entry->fd >= 0) { // add fd to select set
-        FD_SET(client_entry->fd, &selectFDs);
-      }
-    }
-
-    // select call - timeout is set so we can check events at regular intervals
-    int fds_ready = mgmt_select(FD_SETSIZE, &selectFDs, (fd_set *)nullptr, (fd_set *)nullptr, &timeout);
-
-    // check return
-    if (fds_ready > 0) {
-      // we got connections or requests!
-
-      // first check for connections!
-      if (con_socket_fd >= 0 && FD_ISSET(con_socket_fd, &selectFDs)) {
-        fds_ready--;
-
-        // create a new instance of the fd to alarms registered mapping
-        EventClientT *new_client_con = new_event_client();
-
-        if (!new_client_con) {
-          // Debug ("TS_Control_Main", "can't create new EventClientT for new connection");
-        } else {
-          // accept connection
-          socklen_t addr_len = (sizeof(struct sockaddr));
-          new_con_fd         = mgmt_accept(con_socket_fd, new_client_con->adr, &addr_len);
-          new_client_con->fd = new_con_fd;
-          accepted_clients.emplace(new_client_con->fd, new_client_con);
-          Debug("event", "[event_callback_main] Accept new connection: fd=%d", new_con_fd);
-        }
-      } // end if (new_con_fd >= 0 && FD_ISSET(new_con_fd, &selectFDs))
-
-      // some other file descriptor; for each one, service request
-      if (fds_ready > 0) { // RECEIVED A REQUEST from remote API client
-        // see if there are more fd to set - iterate through all entries in hash table
-        for (auto it = accepted_clients.begin(); it != accepted_clients.end();) {
-          client_entry = it->second;
-          ++it; // prevent the breaking of remove_event_client
-          // got information check
-          if (client_entry->fd && FD_ISSET(client_entry->fd, &selectFDs)) {
-            // SERVICE REQUEST - read the op and message into a buffer
-            // clear the fields first
-            void *req;
-            size_t reqlen;
-
-            ret = preprocess_msg(client_entry->fd, &req, &reqlen);
-            if (ret == TS_ERR_NET_READ || ret == TS_ERR_NET_EOF) { // preprocess_msg FAILED!
-              Debug("event", "[event_callback_main] preprocess_msg FAILED; skip!");
-              remove_event_client(client_entry, accepted_clients);
-              continue;
-            }
-
-            ret = handle_event_message(client_entry, req, reqlen);
-            ats_free(req);
-
-            if (ret == TS_ERR_NET_WRITE || ret == TS_ERR_NET_EOF) {
-              Debug("event", "[event_callback_main] ERROR: handle_control_message");
-              remove_event_client(client_entry, accepted_clients);
-              continue;
-            }
-
-          } // end if(client_entry->fd && FD_ISSET(client_entry->fd, &selectFDs))
-        }   // end for (auto it = accepted_clients.begin(); it != accepted_clients.end();)
-      }     // end if (fds_ready > 0)
-
-    } // end if (fds_ready > 0)
-
-    // ------------ service loop is done, check for events now -------------
-    // for each event in the mgmt_events list, uses the event id to check the
-    // events_registered queue for each client connection to see if that client
-    // has a callback registered for that event_id
-
-    TSMgmtEvent *event;
-
-    if (!mgmt_events || queue_is_empty(mgmt_events)) { // no events to process
-      // fprintf(stderr, "[event_callback_main] NO EVENTS TO PROCESS\n");
-      Debug("event", "[event_callback_main] NO EVENTS TO PROCESS");
-      continue;
-    }
-    // iterate through each event in mgmt_events
-    while (!queue_is_empty(mgmt_events)) {
-      ink_mutex_acquire(&mgmt_events_lock);                     // acquire lock
-      event = static_cast<TSMgmtEvent *>(dequeue(mgmt_events)); // get what we want
-      ink_mutex_release(&mgmt_events_lock);                     // release lock
-
-      if (!event) {
-        continue;
-      }
-
-      // fprintf(stderr, "[event_callback_main] have an EVENT TO PROCESS\n");
-
-      // iterate through all entries in hash table, if any
-      for (auto &&it : accepted_clients) {
-        client_entry = it.second;
-        if (client_entry->events_registered[event->id]) {
-          OpType optype           = OpType::EVENT_NOTIFY;
-          MgmtMarshallString name = event->name;
-          MgmtMarshallString desc = event->description;
-
-          ret = send_mgmt_request(client_entry->fd, OpType::EVENT_NOTIFY, &optype, &name, &desc);
-          if (ret != TS_ERR_OKAY) {
-            Debug("event", "sending even notification to fd [%d] failed.", client_entry->fd);
-          }
-        }
-        // get next client connection, if any
-      } // end while(con_entry)
-
-      // now we can delete the event
-      // fprintf(stderr, "[event_callback_main] DELETE EVENT\n");
-      TSEventDestroy(event);
-    } // end while (!queue_is_empty)
-
-  } // end while (1)
-
-  // delete tables
-  delete_mgmt_events();
-
-  // iterate through hash table; close client socket connections and remove entry
-  for (auto &&it : accepted_clients) {
-    client_entry = it.second;
-    if (client_entry->fd >= 0) {
-      close_socket(client_entry->fd);
-    }
-    accepted_clients.erase(client_entry->fd); // remove binding
-    delete_event_client(client_entry);        // free ClientT
-  }
-  // all entries should be removed and freed already
-  accepted_clients.clear();
-
-  ink_thread_exit(nullptr);
-  return nullptr;
-}
-
-/*-------------------------------------------------------------------------
-                             HANDLER FUNCTIONS
- --------------------------------------------------------------------------*/
-
-/**************************************************************************
- * handle_event_reg_callback
- *
- * purpose: handles request to register a callback for a specific event (or all events)
- * input: client - the client currently reading the msg from
- *        req    - the event_name
- * output: TS_ERR_xx
- * note: the req should be the event name; does not send a reply to client
- *************************************************************************/
-static TSMgmtError
-handle_event_reg_callback(EventClientT *client, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallString name = nullptr;
-  TSMgmtError ret;
-
-  ret = recv_mgmt_request(req, reqlen, OpType::EVENT_REG_CALLBACK, &optype, &name);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  // mark the specified alarm as "wanting to be notified" in the client's alarm_registered list
-  if (strlen(name) == 0) { // mark all alarms
-    for (bool &i : client->events_registered) {
-      i = true;
-    }
-  } else {
-    int id = get_event_id(name);
-    if (id < 0) {
-      ret = TS_ERR_FAIL;
-      goto done;
-    }
-
-    client->events_registered[id] = true;
-  }
-
-  ret = TS_ERR_OKAY;
-
-done:
-  ats_free(name);
-  return ret;
-}
-
-/**************************************************************************
- * handle_event_unreg_callback
- *
- * purpose: handles request to unregister a callback for a specific event (or all events)
- * input: client - the client currently reading the msg from
- *        req    - the event_name
- * output: TS_ERR_xx
- * note: the req should be the event name; does not send reply to client
- *************************************************************************/
-static TSMgmtError
-handle_event_unreg_callback(EventClientT *client, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallString name = nullptr;
-  TSMgmtError ret;
-
-  ret = recv_mgmt_request(req, reqlen, OpType::EVENT_UNREG_CALLBACK, &optype, &name);
-  if (ret != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  // mark the specified alarm as "wanting to be notified" in the client's alarm_registered list
-  if (strlen(name) == 0) { // mark all alarms
-    for (bool &i : client->events_registered) {
-      i = false;
-    }
-  } else {
-    int id = get_event_id(name);
-    if (id < 0) {
-      ret = TS_ERR_FAIL;
-      goto done;
-    }
-
-    client->events_registered[id] = false;
-  }
-
-  ret = TS_ERR_OKAY;
-
-done:
-  ats_free(name);
-  return ret;
-}
-
-using event_message_handler = TSMgmtError (*)(EventClientT *, void *, size_t);
-
-static const event_message_handler handlers[] = {
-  nullptr,                     // RECORD_SET
-  nullptr,                     // RECORD_GET
-  nullptr,                     // PROXY_STATE_GET
-  nullptr,                     // PROXY_STATE_SET
-  nullptr,                     // RECONFIGURE
-  nullptr,                     // RESTART
-  nullptr,                     // BOUNCE
-  nullptr,                     // EVENT_RESOLVE
-  nullptr,                     // EVENT_GET_MLT
-  nullptr,                     // EVENT_ACTIVE
-  handle_event_reg_callback,   // EVENT_REG_CALLBACK
-  handle_event_unreg_callback, // EVENT_UNREG_CALLBACK
-  nullptr,                     // EVENT_NOTIFY
-  nullptr,                     // DIAGS
-  nullptr,                     // STATS_RESET_NODE
-  nullptr,                     // STORAGE_DEVICE_CMD_OFFLINE
-  nullptr,                     // RECORD_MATCH_GET
-  nullptr,                     // LIFECYCLE_MESSAGE
-  nullptr,                     // HOST_STATUS_UP
-  nullptr,                     // HOST_STATUS_DOWN
-};
-
-static TSMgmtError
-handle_event_message(EventClientT *client, void *req, size_t reqlen)
-{
-  OpType optype = extract_mgmt_request_optype(req, reqlen);
-
-  if (static_cast<unsigned>(optype) >= countof(handlers)) {
-    goto fail;
-  }
-
-  if (handlers[static_cast<unsigned>(optype)] == nullptr) {
-    goto fail;
-  }
-
-  if (mgmt_has_peereid()) {
-    uid_t euid = -1;
-    gid_t egid = -1;
-
-    // For now, all event messages require privilege. This is compatible with earlier
-    // versions of Traffic Server that always required privilege.
-    if (mgmt_get_peereid(client->fd, &euid, &egid) == -1 || (euid != 0 && euid != geteuid())) {
-      return TS_ERR_PERMISSION_DENIED;
-    }
-  }
-
-  return handlers[static_cast<unsigned>(optype)](client, req, reqlen);
-
-fail:
-  mgmt_elog(0, "%s: missing handler for type %d event message\n", __func__, static_cast<int>(optype));
-  return TS_ERR_PARAMS;
-}
diff --git a/mgmt/api/EventControlMain.h b/mgmt/api/EventControlMain.h
deleted file mode 100644
index df2097c..0000000
--- a/mgmt/api/EventControlMain.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: EventControlMain.h
- * Purpose: Handles event callbacks only
- * Created: 6/26/00
- * Created by: lant
- *
- ***************************************************************************/
-
-#pragma once
-
-#include "mgmtapi.h"       //add the include path b/c included in web dir
-#include "CoreAPIShared.h" // for NUM_EVENTS
-#include "Alarms.h"
-#include "tscore/ink_llqueue.h"
-
-#include <unordered_map>
-
-// use events_registered[event_id] as index to check if alarm is registered
-typedef struct {
-  int fd; // client socket
-  struct sockaddr *adr;
-  bool events_registered[NUM_EVENTS];
-} EventClientT;
-
-EventClientT *new_event_client();
-void delete_event_client(EventClientT *client);
-void remove_event_client(EventClientT *client, std::unordered_map<int, EventClientT *> &table);
-
-TSMgmtError init_mgmt_events();
-void delete_mgmt_events();
-void delete_event_queue(LLQ *q);
-
-void apiAlarmCallback(alarm_t newAlarm, char *ip, char *desc);
-void *event_callback_main(void *arg);
diff --git a/mgmt/api/INKMgmtAPI.cc b/mgmt/api/INKMgmtAPI.cc
index 3f4b9dd..38e3892 100644
--- a/mgmt/api/INKMgmtAPI.cc
+++ b/mgmt/api/INKMgmtAPI.cc
@@ -36,8 +36,8 @@
 #include "tscore/I_Layout.h"
 
 #include "mgmtapi.h"
-#include "CoreAPI.h"
 #include "CoreAPIShared.h"
+#include "tscore/ink_llqueue.h"
 
 #include "tscore/TextBuffer.h"
 
@@ -354,491 +354,6 @@
   return true;
 }
 
-/*--- allocate/deallocate operations --------------------------------------*/
-tsapi TSMgmtEvent *
-TSEventCreate(void)
-{
-  TSMgmtEvent *event = static_cast<TSMgmtEvent *>(ats_malloc(sizeof(TSMgmtEvent)));
-
-  event->id          = -1;
-  event->name        = nullptr;
-  event->description = nullptr;
-  event->priority    = TS_EVENT_PRIORITY_UNDEFINED;
-
-  return event;
-}
-
-tsapi void
-TSEventDestroy(TSMgmtEvent *event)
-{
-  if (event) {
-    ats_free(event->name);
-    ats_free(event->description);
-    ats_free(event);
-  }
-  return;
-}
-
-tsapi TSRecordEle *
-TSRecordEleCreate(void)
-{
-  TSRecordEle *ele = static_cast<TSRecordEle *>(ats_malloc(sizeof(TSRecordEle)));
-
-  ele->rec_name = nullptr;
-  ele->rec_type = TS_REC_UNDEFINED;
-
-  return ele;
-}
-
-tsapi void
-TSRecordEleDestroy(TSRecordEle *ele)
-{
-  if (ele) {
-    ats_free(ele->rec_name);
-    if (ele->rec_type == TS_REC_STRING && ele->valueT.string_val) {
-      ats_free(ele->valueT.string_val);
-    }
-    ats_free(ele);
-  }
-  return;
-}
-
-/***************************************************************************
- * API Core
- ***************************************************************************/
-
-/*--- host status operations ----------------------------------------------- */
-tsapi TSMgmtError
-TSHostStatusSetUp(const char *host_name, int down_time, const char *reason)
-{
-  return HostStatusSetUp(host_name, down_time, reason);
-}
-
-tsapi TSMgmtError
-TSHostStatusSetDown(const char *host_name, int down_time, const char *reason)
-{
-  return HostStatusSetDown(host_name, down_time, reason);
-}
-
-/*--- statistics operations ----------------------------------------------- */
-tsapi TSMgmtError
-TSStatsReset(const char *name)
-{
-  return StatsReset(name);
-}
-
-/*--- variable operations ------------------------------------------------- */
-/* Call the CfgFileIO variable operations */
-
-tsapi TSMgmtError
-TSRecordGet(const char *rec_name, TSRecordEle *rec_val)
-{
-  return MgmtRecordGet(rec_name, rec_val);
-}
-
-TSMgmtError
-TSRecordGetInt(const char *rec_name, TSInt *int_val)
-{
-  TSMgmtError ret = TS_ERR_OKAY;
-
-  TSRecordEle *ele = TSRecordEleCreate();
-  ret              = MgmtRecordGet(rec_name, ele);
-  if (ret != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *int_val = ele->valueT.int_val;
-
-END:
-  TSRecordEleDestroy(ele);
-  return ret;
-}
-
-TSMgmtError
-TSRecordGetCounter(const char *rec_name, TSCounter *counter_val)
-{
-  TSMgmtError ret;
-
-  TSRecordEle *ele = TSRecordEleCreate();
-  ret              = MgmtRecordGet(rec_name, ele);
-  if (ret != TS_ERR_OKAY) {
-    goto END;
-  }
-  *counter_val = ele->valueT.counter_val;
-
-END:
-  TSRecordEleDestroy(ele);
-  return ret;
-}
-
-TSMgmtError
-TSRecordGetFloat(const char *rec_name, TSFloat *float_val)
-{
-  TSMgmtError ret;
-
-  TSRecordEle *ele = TSRecordEleCreate();
-  ret              = MgmtRecordGet(rec_name, ele);
-  if (ret != TS_ERR_OKAY) {
-    goto END;
-  }
-  *float_val = ele->valueT.float_val;
-
-END:
-  TSRecordEleDestroy(ele);
-  return ret;
-}
-
-TSMgmtError
-TSRecordGetString(const char *rec_name, TSString *string_val)
-{
-  TSMgmtError ret;
-
-  TSRecordEle *ele = TSRecordEleCreate();
-  ret              = MgmtRecordGet(rec_name, ele);
-  if (ret != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *string_val = ats_strdup(ele->valueT.string_val);
-
-END:
-  TSRecordEleDestroy(ele);
-  return ret;
-}
-
-/*-------------------------------------------------------------------------
- * TSRecordGetMlt
- *-------------------------------------------------------------------------
- * Purpose: Retrieves list of record values specified in the rec_names list
- * Input: rec_names - list of record names to retrieve
- *        rec_vals  - queue of TSRecordEle* that corresponds to rec_names
- * Output: If at any point, while retrieving one of the records there's a
- *         a failure then the entire process is aborted, all the allocated
- *         TSRecordEle's are deallocated and TS_ERR_FAIL is returned.
- * Note: rec_names is not freed; if function is successful, the rec_names
- *       list is unchanged!
- *
- * IS THIS FUNCTION AN ATOMIC TRANSACTION? Technically, all the variables
- * requested should refer to the same config file. But a lock is only
- * put on each variable it is looked up. Need to be able to lock
- * a file while retrieving all the requested records!
- */
-
-tsapi TSMgmtError
-TSRecordGetMlt(TSStringList rec_names, TSList rec_vals)
-{
-  int num_recs, i, j;
-  TSMgmtError ret;
-
-  if (!rec_names || !rec_vals) {
-    return TS_ERR_PARAMS;
-  }
-
-  num_recs = queue_len(static_cast<LLQ *>(rec_names));
-  for (i = 0; i < num_recs; i++) {
-    char *rec_name = static_cast<char *>(dequeue(static_cast<LLQ *>(rec_names))); // remove name from list
-    if (!rec_name) {
-      return TS_ERR_PARAMS; // NULL is invalid record name
-    }
-
-    TSRecordEle *ele = TSRecordEleCreate();
-
-    ret = MgmtRecordGet(rec_name, ele);
-    enqueue(static_cast<LLQ *>(rec_names), rec_name); // return name to list
-
-    if (ret != TS_ERR_OKAY) { // RecordGet failed
-      // need to free all the ele's allocated by MgmtRecordGet so far
-      TSRecordEleDestroy(ele);
-      for (j = 0; j < i; j++) {
-        ele = static_cast<TSRecordEle *>(dequeue(static_cast<LLQ *>(rec_vals)));
-        if (ele) {
-          TSRecordEleDestroy(ele);
-        }
-      }
-      return ret;
-    }
-    enqueue(static_cast<LLQ *>(rec_vals), ele); // all is good; add ele to end of list
-  }
-
-  return TS_ERR_OKAY;
-}
-
-tsapi TSMgmtError
-TSRecordGetMatchMlt(const char *regex, TSList rec_vals)
-{
-  if (!regex || !rec_vals) {
-    return TS_ERR_PARAMS;
-  }
-
-  return MgmtRecordGetMatching(regex, rec_vals);
-}
-
-tsapi TSMgmtError
-TSRecordSet(const char *rec_name, const char *val, TSActionNeedT *action_need)
-{
-  return MgmtRecordSet(rec_name, val, action_need);
-}
-
-tsapi TSMgmtError
-TSRecordSetInt(const char *rec_name, TSInt int_val, TSActionNeedT *action_need)
-{
-  return MgmtRecordSetInt(rec_name, int_val, action_need);
-}
-
-tsapi TSMgmtError
-TSRecordSetCounter(const char *rec_name, TSCounter counter_val, TSActionNeedT *action_need)
-{
-  return MgmtRecordSetCounter(rec_name, counter_val, action_need);
-}
-
-tsapi TSMgmtError
-TSRecordSetFloat(const char *rec_name, TSFloat float_val, TSActionNeedT *action_need)
-{
-  return MgmtRecordSetFloat(rec_name, float_val, action_need);
-}
-
-tsapi TSMgmtError
-TSRecordSetString(const char *rec_name, const char *str_val, TSActionNeedT *action_need)
-{
-  return MgmtRecordSetString(rec_name, str_val, action_need);
-}
-
-/*-------------------------------------------------------------------------
- * TSRecordSetMlt
- *-------------------------------------------------------------------------
- * Basically iterates through each RecordEle in rec_list and calls the
- * appropriate "MgmtRecordSetxx" function for that record
- * Input: rec_list - queue of TSRecordEle*; each TSRecordEle* must have
- *        a valid record name (remains unchanged on return)
- * Output: if there is an error during the setting of one of the variables then
- *         will continue to try to set the other variables. Error response will
- *         indicate though that not all set operations were successful.
- *         TS_ERR_OKAY is returned if all the records are set successfully
- * Note: Determining the action needed is more complex b/c need to keep
- * track of which record change is the most drastic out of the group of
- * records; action_need will be set to the most severe action needed of
- * all the "Set" calls
- */
-tsapi TSMgmtError
-TSRecordSetMlt(TSList rec_list, TSActionNeedT *action_need)
-{
-  int num_recs, ret, i;
-  TSMgmtError status           = TS_ERR_OKAY;
-  TSActionNeedT top_action_req = TS_ACTION_UNDEFINED;
-
-  if (!rec_list || !action_need) {
-    return TS_ERR_PARAMS;
-  }
-
-  num_recs = queue_len(static_cast<LLQ *>(rec_list));
-
-  for (i = 0; i < num_recs; i++) {
-    TSRecordEle *ele = static_cast<TSRecordEle *>(dequeue(static_cast<LLQ *>(rec_list)));
-    if (ele) {
-      switch (ele->rec_type) {
-      case TS_REC_INT:
-        ret = MgmtRecordSetInt(ele->rec_name, ele->valueT.int_val, action_need);
-        break;
-      case TS_REC_COUNTER:
-        ret = MgmtRecordSetCounter(ele->rec_name, ele->valueT.counter_val, action_need);
-        break;
-      case TS_REC_FLOAT:
-        ret = MgmtRecordSetFloat(ele->rec_name, ele->valueT.float_val, action_need);
-        break;
-      case TS_REC_STRING:
-        ret = MgmtRecordSetString(ele->rec_name, ele->valueT.string_val, action_need);
-        break;
-      default:
-        ret = TS_ERR_FAIL;
-        break;
-      }; /* end of switch (ele->rec_type) */
-      if (ret != TS_ERR_OKAY) {
-        status = TS_ERR_FAIL;
-      }
-
-      // keep track of most severe action; reset if needed
-      // the TSActionNeedT should be listed such that most severe actions have
-      // a lower number (so most severe action == 0)
-      if (*action_need < top_action_req) { // a more severe action
-        top_action_req = *action_need;
-      }
-    }
-    enqueue(static_cast<LLQ *>(rec_list), ele);
-  }
-
-  // set the action_need to be the most sever action needed of all the "set" calls
-  *action_need = top_action_req;
-
-  return status;
-}
-
-/*--- api initialization and shutdown -------------------------------------*/
-tsapi TSMgmtError
-TSInit(const char *socket_path, TSInitOptionT options)
-{
-  return Init(socket_path, options);
-}
-
-tsapi TSMgmtError
-TSTerminate()
-{
-  return Terminate();
-}
-
-/*--- plugin initialization -----------------------------------------------*/
-inkexp extern void
-TSPluginInit(int /* argc ATS_UNUSED */, const char * /* argv ATS_UNUSED */[])
-{
-}
-
-/*--- network operations --------------------------------------------------*/
-tsapi TSMgmtError
-TSConnect(TSIpAddr /* ip_addr ATS_UNUSED */, int /* port ATS_UNUSED */)
-{
-  return TS_ERR_OKAY;
-}
-tsapi TSMgmtError
-TSDisconnectCbRegister(TSDisconnectFunc * /* func ATS_UNUSED */, void * /* data ATS_UNUSED */)
-{
-  return TS_ERR_OKAY;
-}
-tsapi TSMgmtError
-TSDisconnectRetrySet(int /* retries ATS_UNUSED */, int /* retry_sleep_msec ATS_UNUSED */)
-{
-  return TS_ERR_OKAY;
-}
-tsapi TSMgmtError
-TSDisconnect()
-{
-  return TS_ERR_OKAY;
-}
-
-/*--- control operations --------------------------------------------------*/
-/* NOTE: these operations are wrappers that make direct calls to the CoreAPI */
-
-/* TSProxyStateGet: get the proxy state (on/off)
- * Input:  <none>
- * Output: proxy state (on/off)
- */
-tsapi TSProxyStateT
-TSProxyStateGet()
-{
-  return ProxyStateGet();
-}
-
-/* TSProxyStateSet: set the proxy state (on/off)
- * Input:  proxy_state - set to on/off
- *         clear - start TS with cache clearing option,
- *                 when stopping TS should always be TS_CACHE_CLEAR_NONE
- * Output: TSMgmtError
- */
-tsapi TSMgmtError
-TSProxyStateSet(TSProxyStateT proxy_state, unsigned clear)
-{
-  unsigned mask = TS_CACHE_CLEAR_NONE | TS_CACHE_CLEAR_CACHE | TS_CACHE_CLEAR_HOSTDB;
-
-  if (clear & ~mask) {
-    return TS_ERR_PARAMS;
-  }
-
-  return ProxyStateSet(proxy_state, static_cast<TSCacheClearT>(clear));
-}
-
-tsapi TSMgmtError
-TSProxyBacktraceGet(unsigned options, TSString *trace)
-{
-  if (options != 0) {
-    return TS_ERR_PARAMS;
-  }
-
-  if (trace == nullptr) {
-    return TS_ERR_PARAMS;
-  }
-
-  return ServerBacktrace(options, trace);
-}
-
-/* TSReconfigure: tell traffic_server to re-read its configuration files
- * Input:  <none>
- * Output: TSMgmtError
- */
-tsapi TSMgmtError
-TSReconfigure()
-{
-  return Reconfigure();
-}
-
-/* TSRestart: restarts Traffic Server
- * Input:  options - bitmask of TSRestartOptionT
- * Output: TSMgmtError
- */
-tsapi TSMgmtError
-TSRestart(unsigned options)
-{
-  return Restart(options);
-}
-
-/* TSActionDo: based on TSActionNeedT, will take appropriate action
- * Input: action - action that needs to be taken
- * Output: TSMgmtError
- */
-tsapi TSMgmtError
-TSActionDo(TSActionNeedT action)
-{
-  TSMgmtError ret;
-
-  switch (action) {
-  case TS_ACTION_RESTART:
-    ret = Restart(true); // cluster wide by default?
-    break;
-  case TS_ACTION_RECONFIGURE:
-    ret = Reconfigure();
-    break;
-  case TS_ACTION_DYNAMIC:
-    /* do nothing - change takes effect immediately */
-    return TS_ERR_OKAY;
-  case TS_ACTION_SHUTDOWN:
-  default:
-    return TS_ERR_FAIL;
-  }
-
-  return ret;
-}
-
-/* TSBouncer: restarts the traffic_server process(es)
- * Input:  options - bitmask of TSRestartOptionT
- * Output: TSMgmtError
- */
-tsapi TSMgmtError
-TSBounce(unsigned options)
-{
-  return Bounce(options);
-}
-
-tsapi TSMgmtError
-TSStop(unsigned options)
-{
-  return Stop(options);
-}
-
-tsapi TSMgmtError
-TSDrain(unsigned options)
-{
-  return Drain(options);
-}
-
-tsapi TSMgmtError
-TSStorageDeviceCmdOffline(const char *dev)
-{
-  return StorageDeviceCmdOffline(dev);
-}
-
-tsapi TSMgmtError
-TSLifecycleMessage(const char *tag, void const *data, size_t data_size)
-{
-  return LifecycleMessage(tag, data, data_size);
-}
-
 /* NOTE: user must deallocate the memory for the string returned */
 char *
 TSGetErrorMessage(TSMgmtError err_id)
@@ -1010,310 +525,3 @@
 
   return status;
 }
-
-/*--- cache inspector operations -------------------------------------------*/
-
-tsapi TSMgmtError
-TSLookupFromCacheUrl(TSString url, TSString *info)
-{
-  TSMgmtError err = TS_ERR_OKAY;
-  int fd;
-  char request[BUFSIZE];
-  char response[URL_BUFSIZE];
-  char *header;
-  char *body;
-  int hdr_size;
-  int bdy_size;
-  int timeout   = URL_TIMEOUT;
-  TSInt ts_port = 8080;
-
-  if ((err = TSRecordGetInt("proxy.config.http.server_port", &ts_port)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((fd = connectDirect("localhost", ts_port, timeout)) < 0) {
-    err = TS_ERR_FAIL;
-    goto END;
-  }
-  snprintf(request, BUFSIZE, "http://{cache}/lookup_url?url=%s", url);
-  if ((err = sendHTTPRequest(fd, request, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  memset(response, 0, URL_BUFSIZE);
-  if ((err = readHTTPResponse(fd, response, URL_BUFSIZE, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((err = parseHTTPResponse(response, &header, &hdr_size, &body, &bdy_size)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *info = ats_strndup(body, bdy_size);
-
-END:
-  return err;
-}
-
-tsapi TSMgmtError
-TSLookupFromCacheUrlRegex(TSString url_regex, TSString *list)
-{
-  TSMgmtError err = TS_ERR_OKAY;
-  int fd          = -1;
-  char request[BUFSIZE];
-  char response[URL_BUFSIZE];
-  char *header;
-  char *body;
-  int hdr_size;
-  int bdy_size;
-  int timeout   = -1;
-  TSInt ts_port = 8080;
-
-  if ((err = TSRecordGetInt("proxy.config.http.server_port", &ts_port)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((fd = connectDirect("localhost", ts_port, timeout)) < 0) {
-    err = TS_ERR_FAIL;
-    goto END;
-  }
-  snprintf(request, BUFSIZE, "http://{cache}/lookup_regex?url=%s", url_regex);
-  if ((err = sendHTTPRequest(fd, request, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  memset(response, 0, URL_BUFSIZE);
-  if ((err = readHTTPResponse(fd, response, URL_BUFSIZE, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((err = parseHTTPResponse(response, &header, &hdr_size, &body, &bdy_size)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *list = ats_strndup(body, bdy_size);
-END:
-  return err;
-}
-
-tsapi TSMgmtError
-TSDeleteFromCacheUrl(TSString url, TSString *info)
-{
-  TSMgmtError err = TS_ERR_OKAY;
-  int fd          = -1;
-  char request[BUFSIZE];
-  char response[URL_BUFSIZE];
-  char *header;
-  char *body;
-  int hdr_size;
-  int bdy_size;
-  int timeout   = URL_TIMEOUT;
-  TSInt ts_port = 8080;
-
-  if ((err = TSRecordGetInt("proxy.config.http.server_port", &ts_port)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((fd = connectDirect("localhost", ts_port, timeout)) < 0) {
-    err = TS_ERR_FAIL;
-    goto END;
-  }
-  snprintf(request, BUFSIZE, "http://{cache}/delete_url?url=%s", url);
-  if ((err = sendHTTPRequest(fd, request, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  memset(response, 0, URL_BUFSIZE);
-  if ((err = readHTTPResponse(fd, response, URL_BUFSIZE, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((err = parseHTTPResponse(response, &header, &hdr_size, &body, &bdy_size)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *info = ats_strndup(body, bdy_size);
-
-END:
-  return err;
-}
-
-tsapi TSMgmtError
-TSDeleteFromCacheUrlRegex(TSString url_regex, TSString *list)
-{
-  TSMgmtError err = TS_ERR_OKAY;
-  int fd          = -1;
-  char request[BUFSIZE];
-  char response[URL_BUFSIZE];
-  char *header;
-  char *body;
-  int hdr_size;
-  int bdy_size;
-  int timeout   = -1;
-  TSInt ts_port = 8080;
-
-  if ((err = TSRecordGetInt("proxy.config.http.server_port", &ts_port)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((fd = connectDirect("localhost", ts_port, timeout)) < 0) {
-    err = TS_ERR_FAIL;
-    goto END;
-  }
-  snprintf(request, BUFSIZE, "http://{cache}/delete_regex?url=%s", url_regex);
-  if ((err = sendHTTPRequest(fd, request, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  memset(response, 0, URL_BUFSIZE);
-  if ((err = readHTTPResponse(fd, response, URL_BUFSIZE, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((err = parseHTTPResponse(response, &header, &hdr_size, &body, &bdy_size)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *list = ats_strndup(body, bdy_size);
-END:
-  return err;
-}
-
-tsapi TSMgmtError
-TSInvalidateFromCacheUrlRegex(TSString url_regex, TSString *list)
-{
-  TSMgmtError err = TS_ERR_OKAY;
-  int fd          = -1;
-  char request[BUFSIZE];
-  char response[URL_BUFSIZE];
-  char *header;
-  char *body;
-  int hdr_size;
-  int bdy_size;
-  int timeout   = -1;
-  TSInt ts_port = 8080;
-
-  if ((err = TSRecordGetInt("proxy.config.http.server_port", &ts_port)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((fd = connectDirect("localhost", ts_port, timeout)) < 0) {
-    err = TS_ERR_FAIL;
-    goto END;
-  }
-  snprintf(request, BUFSIZE, "http://{cache}/invalidate_regex?url=%s", url_regex);
-  if ((err = sendHTTPRequest(fd, request, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  memset(response, 0, URL_BUFSIZE);
-  if ((err = readHTTPResponse(fd, response, URL_BUFSIZE, static_cast<uint64_t>(timeout))) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  if ((err = parseHTTPResponse(response, &header, &hdr_size, &body, &bdy_size)) != TS_ERR_OKAY) {
-    goto END;
-  }
-
-  *list = ats_strndup(body, bdy_size);
-END:
-  return err;
-}
-
-/*--- events --------------------------------------------------------------*/
-tsapi TSMgmtError
-TSEventSignal(char *event_name, ...)
-{
-  va_list ap;
-  TSMgmtError ret;
-
-  va_start(ap, event_name); // initialize the argument pointer ap
-  ret = EventSignal(event_name, ap);
-  va_end(ap);
-  return ret;
-}
-
-tsapi TSMgmtError
-TSEventResolve(const char *event_name)
-{
-  return EventResolve(event_name);
-}
-
-tsapi TSMgmtError
-TSActiveEventGetMlt(TSList active_events)
-{
-  return ActiveEventGetMlt(static_cast<LLQ *>(active_events));
-}
-
-tsapi TSMgmtError
-TSEventIsActive(char *event_name, bool *is_current)
-{
-  return EventIsActive(event_name, is_current);
-}
-
-tsapi TSMgmtError
-TSEventSignalCbRegister(char *event_name, TSEventSignalFunc func, void *data)
-{
-  return EventSignalCbRegister(event_name, func, data);
-}
-
-tsapi TSMgmtError
-TSEventSignalCbUnregister(char *event_name, TSEventSignalFunc func)
-{
-  return EventSignalCbUnregister(event_name, func);
-}
-
-TSConfigRecordDescription *
-TSConfigRecordDescriptionCreate(void)
-{
-  TSConfigRecordDescription *val = static_cast<TSConfigRecordDescription *>(ats_malloc(sizeof(TSConfigRecordDescription)));
-
-  ink_zero(*val);
-  val->rec_type = TS_REC_UNDEFINED;
-
-  return val;
-}
-
-void
-TSConfigRecordDescriptionDestroy(TSConfigRecordDescription *val)
-{
-  TSConfigRecordDescriptionFree(val);
-  ats_free(val);
-}
-
-void
-TSConfigRecordDescriptionFree(TSConfigRecordDescription *val)
-{
-  if (val) {
-    ats_free(val->rec_name);
-    ats_free(val->rec_checkexpr);
-
-    if (val->rec_type == TS_REC_STRING) {
-      ats_free(val->rec_value.string_val);
-    }
-
-    ink_zero(*val);
-    val->rec_type = TS_REC_UNDEFINED;
-  }
-}
-
-TSMgmtError
-TSConfigRecordDescribe(const char *rec_name, unsigned flags, TSConfigRecordDescription *val)
-{
-  if (!rec_name || !val) {
-    return TS_ERR_PARAMS;
-  }
-
-  TSConfigRecordDescriptionFree(val);
-  return MgmtConfigRecordDescribe(rec_name, flags, val);
-}
-
-TSMgmtError
-TSConfigRecordDescribeMatchMlt(const char *rec_regex, unsigned flags, TSList rec_vals)
-{
-  if (!rec_regex || !rec_vals) {
-    return TS_ERR_PARAMS;
-  }
-
-  return MgmtConfigRecordDescribeMatching(rec_regex, flags, rec_vals);
-}
diff --git a/mgmt/api/Makefile.am b/mgmt/api/Makefile.am
index 40890dc..a90c8d9 100644
--- a/mgmt/api/Makefile.am
+++ b/mgmt/api/Makefile.am
@@ -28,62 +28,17 @@
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
 	-I$(abs_top_srcdir)/mgmt/api/include \
+        @SWOC_INCLUDES@ \
 	$(TS_INCLUDES) \
 	$(LIBUNWIND_CFLAGS)
 
-noinst_LTLIBRARIES = libmgmtapilocal.la libmgmtapi.la
-lib_LTLIBRARIES = libtsmgmt.la
+noinst_LTLIBRARIES = libmgmtapi.la
 
 libmgmtapi_la_SOURCES = \
-	CoreAPI.h \
 	CoreAPIShared.cc \
 	CoreAPIShared.h \
-	EventCallback.cc \
-	EventCallback.h \
 	INKMgmtAPI.cc \
-	NetworkMessage.cc \
-	NetworkMessage.h \
 	include/mgmtapi.h
 
-if BUILD_TESTS
-noinst_PROGRAMS = traffic_api_cli_remote
-endif
-
-libmgmtapilocal_la_SOURCES = \
-	CoreAPI.cc \
-	EventControlMain.cc \
-	EventControlMain.h \
-	NetworkUtilsLocal.cc \
-	NetworkUtilsLocal.h \
-	TSControlMain.cc \
-	TSControlMain.h
-
-libtsmgmt_la_SOURCES = \
-	CoreAPIRemote.cc \
-	NetworkUtilsRemote.cc \
-	NetworkUtilsRemote.h
-
-libmgmtapilocal_la_LIBADD = \
-	libmgmtapi.la \
-	$(top_builddir)/src/tscore/libtscore.la
-
-libtsmgmt_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_VERSION@
-libtsmgmt_la_LIBADD = @LIBOBJS@ \
-	libmgmtapi.la \
-	$(top_builddir)/src/tscore/libtscore.la \
-	$(top_builddir)/mgmt/utils/libutils_p.la
-
-if BUILD_TESTS
-traffic_api_cli_remote_SOURCES = APITestCliRemote.cc
-endif
-
-traffic_api_cli_remote_LDADD = \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	libtsmgmt.la \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	$(top_builddir)/src/tscore/libtscore.la \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	@OPENSSL_LIBS@
-
 clang-tidy-local: $(DIST_SOURCES)
 	$(CXX_Clang_Tidy)
diff --git a/mgmt/api/NetworkMessage.cc b/mgmt/api/NetworkMessage.cc
deleted file mode 100644
index 9948908..0000000
--- a/mgmt/api/NetworkMessage.cc
+++ /dev/null
@@ -1,372 +0,0 @@
-/** @file
-
-  Network message marshalling.
-
-  @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 "tscore/ink_config.h"
-#include "tscore/ink_defs.h"
-#include "tscore/ink_error.h"
-#include "tscore/ink_assert.h"
-#include "tscore/ink_memory.h"
-#include "mgmtapi.h"
-#include "NetworkMessage.h"
-
-#define MAX_OPERATION_FIELDS 16
-
-struct NetCmdOperation {
-  unsigned nfields;
-  const MgmtMarshallType fields[MAX_OPERATION_FIELDS];
-};
-
-// Requests always begin with a OpType, followed by additional fields.
-static const struct NetCmdOperation requests[] = {
-  /* RECORD_SET                 */ {3, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING}},
-  /* RECORD_GET                 */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* PROXY_STATE_GET            */ {1, {MGMT_MARSHALL_INT}},
-  /* PROXY_STATE_SET            */ {3, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* RECONFIGURE                */ {1, {MGMT_MARSHALL_INT}},
-  /* RESTART                    */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* BOUNCE                     */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* STOP                       */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* DRAIN                      */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* EVENT_RESOLVE              */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* EVENT_GET_MLT              */ {1, {MGMT_MARSHALL_INT}},
-  /* EVENT_ACTIVE               */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* EVENT_REG_CALLBACK         */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* EVENT_UNREG_CALLBACK       */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* EVENT_NOTIFY               */ {3, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING}}, // only msg sent from TM to
-                                                                                                         // client
-  /* STATS_RESET_NODE           */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* STORAGE_DEVICE_CMD_OFFLINE */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* RECORD_MATCH_GET           */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* API_PING                   */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* SERVER_BACKTRACE           */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* RECORD_DESCRIBE_CONFIG     */ {3, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT}},
-  /* LIFECYCLE_MESSAGE          */ {3, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_DATA}},
-  /* HOST_STATUS_HOST_UP        */ {4, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT}},
-  /* HOST_STATUS_HOST_DOWN      */ {4, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT}},
-};
-
-// Responses always begin with a TSMgmtError code, followed by additional fields.
-static const struct NetCmdOperation responses[] = {
-  /* RECORD_SET                 */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* RECORD_GET                 */
-  {5, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT, MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_DATA}},
-  /* PROXY_STATE_GET            */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* PROXY_STATE_SET            */ {1, {MGMT_MARSHALL_INT}},
-  /* RECONFIGURE                */ {1, {MGMT_MARSHALL_INT}},
-  /* RESTART                    */ {1, {MGMT_MARSHALL_INT}},
-  /* BOUNCE                     */ {1, {MGMT_MARSHALL_INT}},
-  /* STOP                       */ {1, {MGMT_MARSHALL_INT}},
-  /* DRAIN                      */ {1, {MGMT_MARSHALL_INT}},
-  /* EVENT_RESOLVE              */ {1, {MGMT_MARSHALL_INT}},
-  /* EVENT_GET_MLT              */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* EVENT_ACTIVE               */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT}},
-  /* EVENT_REG_CALLBACK         */ {0, {}}, // no reply
-  /* EVENT_UNREG_CALLBACK       */ {0, {}}, // no reply
-  /* EVENT_NOTIFY               */ {0, {}}, // no reply
-  /* STATS_RESET_NODE           */ {1, {MGMT_MARSHALL_INT}},
-  /* STORAGE_DEVICE_CMD_OFFLINE */ {1, {MGMT_MARSHALL_INT}},
-  /* RECORD_MATCH_GET           */
-  {5, {MGMT_MARSHALL_INT, MGMT_MARSHALL_INT, MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_DATA}},
-  /* API_PING                   */ {0, {}}, // no reply
-  /* SERVER_BACKTRACE           */ {2, {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING}},
-  /* RECORD_DESCRIBE_CONFIG     */
-  {15,
-   {MGMT_MARSHALL_INT /* status */, MGMT_MARSHALL_STRING /* name */, MGMT_MARSHALL_DATA /* value */,
-    MGMT_MARSHALL_DATA /* default */, MGMT_MARSHALL_INT /* type */, MGMT_MARSHALL_INT /* class */, MGMT_MARSHALL_INT /* version */,
-    MGMT_MARSHALL_INT /* rsb */, MGMT_MARSHALL_INT /* order */, MGMT_MARSHALL_INT /* access */, MGMT_MARSHALL_INT /* update */,
-    MGMT_MARSHALL_INT /* updatetype */, MGMT_MARSHALL_INT /* checktype */, MGMT_MARSHALL_INT /* source */,
-    MGMT_MARSHALL_STRING /* checkexpr */}},
-  /* LIFECYCLE_MESSAGE          */ {1, {MGMT_MARSHALL_INT}},
-  /* HOST_STATUS_UP             */ {1, {MGMT_MARSHALL_INT}},
-  /* HOST_STATUS_DOWN           */ {1, {MGMT_MARSHALL_INT}},
-};
-
-#define GETCMD(ops, optype, cmd)                           \
-  do {                                                     \
-    if (static_cast<unsigned>(optype) >= countof(ops)) {   \
-      return TS_ERR_PARAMS;                                \
-    }                                                      \
-    if (ops[static_cast<unsigned>(optype)].nfields == 0) { \
-      return TS_ERR_PARAMS;                                \
-    }                                                      \
-    cmd = &ops[static_cast<unsigned>(optype)];             \
-  } while (0);
-
-TSMgmtError
-send_mgmt_request(const mgmt_message_sender &snd, OpType optype, ...)
-{
-  va_list ap;
-  ats_scoped_mem<char> msgbuf;
-  MgmtMarshallInt msglen;
-  const MgmtMarshallType lenfield[] = {MGMT_MARSHALL_INT};
-  const NetCmdOperation *cmd;
-
-  if (!snd.is_connected()) {
-    return TS_ERR_NET_ESTABLISH; // no connection.
-  }
-
-  GETCMD(requests, optype, cmd);
-
-  va_start(ap, optype);
-  msglen = mgmt_message_length_v(cmd->fields, cmd->nfields, ap);
-  va_end(ap);
-
-  msgbuf = static_cast<char *>(ats_malloc(msglen + 4));
-
-  // First marshall the total message length.
-  mgmt_message_marshall((char *)msgbuf, msglen, lenfield, countof(lenfield), &msglen);
-
-  // Now marshall the message itself.
-  va_start(ap, optype);
-  if (mgmt_message_marshall_v((char *)msgbuf + 4, msglen, cmd->fields, cmd->nfields, ap) == -1) {
-    va_end(ap);
-    return TS_ERR_PARAMS;
-  }
-
-  va_end(ap);
-  return snd.send(msgbuf, msglen + 4);
-}
-
-TSMgmtError
-send_mgmt_request(int fd, OpType optype, ...)
-{
-  va_list ap;
-  MgmtMarshallInt msglen;
-  MgmtMarshallData req            = {nullptr, 0};
-  const MgmtMarshallType fields[] = {MGMT_MARSHALL_DATA};
-  const NetCmdOperation *cmd;
-
-  GETCMD(requests, optype, cmd);
-
-  // Figure out the payload length.
-  va_start(ap, optype);
-  msglen = mgmt_message_length_v(cmd->fields, cmd->nfields, ap);
-  va_end(ap);
-
-  ink_assert(msglen >= 0);
-
-  req.ptr = static_cast<char *>(ats_malloc(msglen));
-  req.len = msglen;
-
-  // Marshall the message itself.
-  va_start(ap, optype);
-  if (mgmt_message_marshall_v(req.ptr, req.len, cmd->fields, cmd->nfields, ap) == -1) {
-    ats_free(req.ptr);
-    va_end(ap);
-    return TS_ERR_PARAMS;
-  }
-
-  va_end(ap);
-
-  MgmtMarshallInt op;
-  MgmtMarshallString name;
-  int down_time;
-  static const MgmtMarshallType fieldso[] = {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT};
-
-  if (mgmt_message_parse(static_cast<void *>(req.ptr), msglen, fieldso, countof(fieldso), &op, &name, &down_time) == -1) {
-    printf("Plugin message - RPC parsing error - message discarded.\n");
-  }
-
-  // Send the response as the payload of a data object.
-  if (mgmt_message_write(fd, fields, countof(fields), &req) == -1) {
-    ats_free(req.ptr);
-    return TS_ERR_NET_WRITE;
-  }
-
-  ats_free(req.ptr);
-  return TS_ERR_OKAY;
-}
-
-TSMgmtError
-send_mgmt_error(int fd, OpType optype, TSMgmtError error)
-{
-  MgmtMarshallInt ecode     = error;
-  MgmtMarshallInt intval    = 0;
-  MgmtMarshallData dataval  = {nullptr, 0};
-  MgmtMarshallString strval = nullptr;
-
-  // Switch on operations, grouped by response format.
-  switch (optype) {
-  case OpType::BOUNCE:
-  case OpType::STOP:
-  case OpType::DRAIN:
-  case OpType::EVENT_RESOLVE:
-  case OpType::LIFECYCLE_MESSAGE:
-  case OpType::PROXY_STATE_SET:
-  case OpType::RECONFIGURE:
-  case OpType::RESTART:
-  case OpType::STATS_RESET_NODE:
-  case OpType::HOST_STATUS_UP:
-  case OpType::HOST_STATUS_DOWN:
-  case OpType::STORAGE_DEVICE_CMD_OFFLINE:
-    ink_release_assert(responses[static_cast<unsigned>(optype)].nfields == 1);
-    return send_mgmt_response(fd, optype, &ecode);
-
-  case OpType::RECORD_SET:
-  case OpType::PROXY_STATE_GET:
-  case OpType::EVENT_ACTIVE:
-    ink_release_assert(responses[static_cast<unsigned>(optype)].nfields == 2);
-    return send_mgmt_response(fd, optype, &ecode, &intval);
-
-  case OpType::EVENT_GET_MLT:
-  case OpType::SERVER_BACKTRACE:
-    ink_release_assert(responses[static_cast<unsigned>(optype)].nfields == 2);
-    return send_mgmt_response(fd, optype, &ecode, &strval);
-
-  case OpType::RECORD_GET:
-  case OpType::RECORD_MATCH_GET:
-    ink_release_assert(responses[static_cast<unsigned>(optype)].nfields == 5);
-    return send_mgmt_response(fd, optype, &ecode, &intval, &intval, &strval, &dataval);
-
-  case OpType::RECORD_DESCRIBE_CONFIG:
-    ink_release_assert(responses[static_cast<unsigned>(optype)].nfields == 15);
-    return send_mgmt_response(fd, optype, &ecode, &strval /* name */, &dataval /* value */, &dataval /* default */,
-                              &intval /* type */, &intval /* class */, &intval /* version */, &intval /* rsb */,
-                              &intval /* order */, &intval /* access */, &intval /* update */, &intval /* updatetype */,
-                              &intval /* checktype */, &intval /* source */, &strval /* checkexpr */);
-
-  case OpType::EVENT_REG_CALLBACK:
-  case OpType::EVENT_UNREG_CALLBACK:
-  case OpType::EVENT_NOTIFY:
-  case OpType::API_PING:
-    /* no response for these */
-    ink_release_assert(responses[static_cast<unsigned>(optype)].nfields == 0);
-    return TS_ERR_OKAY;
-
-  case OpType::UNDEFINED_OP:
-    return TS_ERR_OKAY;
-  }
-
-  // We should never get here unless OpTypes are added without
-  // updating the switch statement above. Don't do that; this
-  // code must be able to handle every OpType.
-
-  ink_fatal("missing generic error support for type %d management message", static_cast<int>(optype));
-  return TS_ERR_FAIL;
-}
-
-// Send a management message response. We don't need to worry about retransmitting the message if we get
-// disconnected, so this is much simpler. We can directly marshall the response as a data object.
-TSMgmtError
-send_mgmt_response(int fd, OpType optype, ...)
-{
-  va_list ap;
-  MgmtMarshallInt msglen;
-  MgmtMarshallData reply          = {nullptr, 0};
-  const MgmtMarshallType fields[] = {MGMT_MARSHALL_DATA};
-  const NetCmdOperation *cmd;
-
-  GETCMD(responses, optype, cmd);
-
-  va_start(ap, optype);
-  msglen = mgmt_message_length_v(cmd->fields, cmd->nfields, ap);
-  va_end(ap);
-
-  ink_assert(msglen >= 0);
-
-  reply.ptr = static_cast<char *>(ats_malloc(msglen));
-  reply.len = msglen;
-
-  // Marshall the message itself.
-  va_start(ap, optype);
-  if (mgmt_message_marshall_v(reply.ptr, reply.len, cmd->fields, cmd->nfields, ap) == -1) {
-    ats_free(reply.ptr);
-    va_end(ap);
-    return TS_ERR_PARAMS;
-  }
-
-  va_end(ap);
-
-  // Send the response as the payload of a data object.
-  if (mgmt_message_write(fd, fields, countof(fields), &reply) == -1) {
-    ats_free(reply.ptr);
-    return TS_ERR_NET_WRITE;
-  }
-
-  ats_free(reply.ptr);
-  return TS_ERR_OKAY;
-}
-
-template <unsigned N>
-static TSMgmtError
-recv_x(const struct NetCmdOperation (&ops)[N], void *buf, size_t buflen, OpType optype, va_list ap)
-{
-  ssize_t msglen;
-  const NetCmdOperation *cmd;
-
-  GETCMD(ops, optype, cmd);
-
-  msglen = mgmt_message_parse_v(buf, buflen, cmd->fields, cmd->nfields, ap);
-  return (msglen == -1) ? TS_ERR_PARAMS : TS_ERR_OKAY;
-}
-
-TSMgmtError
-recv_mgmt_request(void *buf, size_t buflen, OpType optype, ...)
-{
-  TSMgmtError err;
-  va_list ap;
-
-  va_start(ap, optype);
-  err = recv_x(requests, buf, buflen, optype, ap);
-  va_end(ap);
-
-  return err;
-}
-
-TSMgmtError
-recv_mgmt_response(void *buf, size_t buflen, OpType optype, ...)
-{
-  TSMgmtError err;
-  va_list ap;
-
-  va_start(ap, optype);
-  err = recv_x(responses, buf, buflen, optype, ap);
-  va_end(ap);
-
-  return err;
-}
-
-TSMgmtError
-recv_mgmt_message(int fd, MgmtMarshallData &msg)
-{
-  const MgmtMarshallType fields[] = {MGMT_MARSHALL_DATA};
-
-  if (mgmt_message_read(fd, fields, countof(fields), &msg) == -1) {
-    return TS_ERR_NET_READ;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-OpType
-extract_mgmt_request_optype(void *msg, size_t msglen)
-{
-  const MgmtMarshallType fields[] = {MGMT_MARSHALL_INT};
-  MgmtMarshallInt optype;
-
-  if (mgmt_message_parse(msg, msglen, fields, countof(fields), &optype) == -1) {
-    return OpType::UNDEFINED_OP;
-  }
-
-  return static_cast<OpType>(optype);
-}
diff --git a/mgmt/api/NetworkMessage.h b/mgmt/api/NetworkMessage.h
deleted file mode 100644
index 70f15e7..0000000
--- a/mgmt/api/NetworkMessage.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/** @file
-
-  Network message marshalling.
-
-  @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.
- */
-
-#pragma once
-
-#include "MgmtMarshall.h"
-
-#define REMOTE_DELIM ':'
-#define REMOTE_DELIM_STR ":"
-
-#define MAX_CONN_TRIES 10 // maximum number of attempts to reconnect to TM
-
-// the possible operations or msg types sent from remote client to TM
-enum class OpType : MgmtMarshallInt {
-  RECORD_SET,
-  RECORD_GET,
-  PROXY_STATE_GET,
-  PROXY_STATE_SET,
-  RECONFIGURE,
-  RESTART,
-  BOUNCE,
-  STOP,
-  DRAIN,
-  EVENT_RESOLVE,
-  EVENT_GET_MLT,
-  EVENT_ACTIVE,
-  EVENT_REG_CALLBACK,
-  EVENT_UNREG_CALLBACK,
-  EVENT_NOTIFY, /* only msg sent from TM to client */
-  STATS_RESET_NODE,
-  STORAGE_DEVICE_CMD_OFFLINE,
-  RECORD_MATCH_GET,
-  API_PING,
-  SERVER_BACKTRACE,
-  RECORD_DESCRIBE_CONFIG,
-  LIFECYCLE_MESSAGE,
-  HOST_STATUS_UP,
-  HOST_STATUS_DOWN,
-  UNDEFINED_OP /* This must be last */
-};
-
-enum {
-  RECORD_DESCRIBE_FLAGS_MATCH = 0x0001,
-};
-
-struct mgmt_message_sender {
-  virtual TSMgmtError send(void *msg, size_t msglen) const = 0;
-  virtual ~mgmt_message_sender(){};
-
-  // Check if the sender is connected.
-  virtual bool is_connected() const = 0;
-};
-
-// Marshall and send a request, prefixing the message length as a MGMT_MARSHALL_INT.
-TSMgmtError send_mgmt_request(const mgmt_message_sender &snd, OpType optype, ...);
-TSMgmtError send_mgmt_request(int fd, OpType optype, ...);
-
-// Marshall and send an error response for this operation type.
-TSMgmtError send_mgmt_error(int fd, OpType op, TSMgmtError error);
-
-// Parse a request message from a buffer.
-TSMgmtError recv_mgmt_request(void *buf, size_t buflen, OpType optype, ...);
-
-// Marshall and send a response, prefixing the message length as a MGMT_MARSHALL_INT.
-TSMgmtError send_mgmt_response(int fd, OpType optype, ...);
-
-// Parse a response message from a buffer.
-TSMgmtError recv_mgmt_response(void *buf, size_t buflen, OpType optype, ...);
-
-// Pull a management message (either request or response) off the wire.
-TSMgmtError recv_mgmt_message(int fd, MgmtMarshallData &msg);
-
-// Extract the first MGMT_MARSHALL_INT from the buffered message. This is the OpType.
-OpType extract_mgmt_request_optype(void *msg, size_t msglen);
diff --git a/mgmt/api/NetworkUtilsLocal.cc b/mgmt/api/NetworkUtilsLocal.cc
deleted file mode 100644
index 50fb802..0000000
--- a/mgmt/api/NetworkUtilsLocal.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/***************************************************************************
- * NetworkUtilsLocal.cc
- *
- * contains implementation of local networking utility functions, such as
- * unmarshalling requests from a remote client and marshalling replies
- *
- *
- ***************************************************************************/
-
-#include "tscore/ink_platform.h"
-#include "tscore/ink_sock.h"
-#include "tscore/Diags.h"
-#include "MgmtUtils.h"
-#include "MgmtSocket.h"
-#include "MgmtMarshall.h"
-#include "CoreAPIShared.h"
-#include "NetworkUtilsLocal.h"
-#include "NetworkMessage.h"
-
-/**********************************************************************
- * preprocess_msg
- *
- * purpose: reads in all the message; parses the message into header info
- *          (OpType + msg_len) and the request portion (used by the handle_xx fns)
- * input: sock_info - socket msg is read from
- *        msg       - the data from the network message (no OpType or msg_len)
- * output: TS_ERR_xx ( if TS_ERR_OKAY, then parameters set successfully)
- * notes: Since preprocess_msg already removes the OpType and msg_len, this part o
- *        the message is not dealt with by the other parsing functions
- **********************************************************************/
-TSMgmtError
-preprocess_msg(int fd, void **req, size_t *reqlen)
-{
-  TSMgmtError ret;
-  MgmtMarshallData msg;
-
-  *req    = nullptr;
-  *reqlen = 0;
-
-  ret = recv_mgmt_message(fd, msg);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  // We should never receive an empty payload.
-  if (msg.ptr == nullptr) {
-    return TS_ERR_NET_READ;
-  }
-
-  *req    = msg.ptr;
-  *reqlen = msg.len;
-  Debug("ts_main", "[preprocess_msg] read message length = %zd", msg.len);
-  return TS_ERR_OKAY;
-}
diff --git a/mgmt/api/NetworkUtilsLocal.h b/mgmt/api/NetworkUtilsLocal.h
deleted file mode 100644
index d600066..0000000
--- a/mgmt/api/NetworkUtilsLocal.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-#pragma once
-
-#include "tscore/ink_defs.h"
-#include "mgmtapi.h"
-
-/*****************************************************************************
- * Unmarshalling/marshalling
- *****************************************************************************/
-TSMgmtError preprocess_msg(int fd, void **req, size_t *reqlen);
diff --git a/mgmt/api/NetworkUtilsRemote.cc b/mgmt/api/NetworkUtilsRemote.cc
deleted file mode 100644
index 780f497..0000000
--- a/mgmt/api/NetworkUtilsRemote.cc
+++ /dev/null
@@ -1,719 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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 "tscore/ink_config.h"
-#include "tscore/ink_defs.h"
-#include "tscore/ink_sock.h"
-#include "tscore/ink_string.h"
-#include "tscore/ink_memory.h"
-#include "tscore/I_Layout.h"
-#include "NetworkUtilsRemote.h"
-#include "CoreAPI.h"
-#include "CoreAPIShared.h"
-#include "MgmtSocket.h"
-#include "MgmtMarshall.h"
-
-CallbackTable *remote_event_callbacks;
-
-int main_socket_fd  = -1;
-int event_socket_fd = -1;
-
-// need to store for reconnecting scenario
-char *main_socket_path  = nullptr; // "<path>/mgmtapi.sock"
-char *event_socket_path = nullptr; // "<path>/eventapi.sock"
-
-static void *event_callback_thread(void *arg);
-
-/**********************************************************************
- * Socket Helper Functions
- **********************************************************************/
-void
-set_socket_paths(const char *path)
-{
-  // free previously set paths if needed
-  ats_free(main_socket_path);
-  ats_free(event_socket_path);
-
-  // construct paths based on user input
-  // form by replacing "mgmtapi.sock" with "eventapi.sock"
-  if (path) {
-    main_socket_path  = ats_stringdup(Layout::relative_to(path, MGMTAPI_MGMT_SOCKET_NAME));
-    event_socket_path = ats_stringdup(Layout::relative_to(path, MGMTAPI_EVENT_SOCKET_NAME));
-  } else {
-    main_socket_path  = nullptr;
-    event_socket_path = nullptr;
-  }
-
-  return;
-}
-
-/**********************************************************************
- * socket_test
- *
- * purpose: performs socket write to check status of other end of connection
- * input: None
- * output: return false if socket write failed due to some other error
- *         return true if socket write successful
- * notes: send the API_PING test msg
- **********************************************************************/
-static bool
-socket_test(int fd)
-{
-  OpType optype       = OpType::API_PING;
-  MgmtMarshallInt now = time(nullptr);
-
-  if (MGMTAPI_SEND_MESSAGE(fd, OpType::API_PING, &optype, &now) == TS_ERR_OKAY) {
-    return true; // write was successful; connection still open
-  }
-
-  return false;
-}
-
-/***************************************************************************
- * connect
- *
- * purpose: connects to the port on traffic server that listens to mgmt
- *          requests & issues out responses and alerts
- * 1) create and set the client socket_fd; connect to TM
- * 2) create and set the client's event_socket_fd; connect to TM
- * output: TS_ERR_OKAY          - if both sockets successfully connect to TM
- *         TS_ERR_NET_ESTABLISH - at least one unsuccessful connection
- * notes: If connection breaks it is responsibility of client to reconnect
- *        otherwise traffic server will assume mgmt stopped request and
- *        goes back to just sitting and listening for connection.
- ***************************************************************************/
-TSMgmtError
-ts_connect()
-{
-  struct sockaddr_un client_sock;
-  struct sockaddr_un client_event_sock;
-
-  int sockaddr_len;
-
-  // make sure a socket path is set up
-  if (!main_socket_path || !event_socket_path) {
-    goto ERROR;
-  }
-  // make sure the length of main_socket_path do not exceed the sizeof(sun_path)
-  if (strlen(main_socket_path) > sizeof(client_sock.sun_path) - 1) {
-    goto ERROR;
-  }
-  // make sure the length of event_socket_path do not exceed the sizeof(sun_path)
-  if (strlen(event_socket_path) > sizeof(client_event_sock.sun_path) - 1) {
-    goto ERROR;
-  }
-  // create a socket
-  main_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
-  if (main_socket_fd < 0) {
-    goto ERROR; // ERROR - can't open socket
-  }
-  // setup Unix domain socket
-  memset(&client_sock, 0, sizeof(sockaddr_un));
-  client_sock.sun_family = AF_UNIX;
-  ink_strlcpy(client_sock.sun_path, main_socket_path, sizeof(client_sock.sun_path));
-#if defined(darwin) || defined(freebsd)
-  sockaddr_len = sizeof(sockaddr_un);
-#else
-  sockaddr_len = sizeof(client_sock.sun_family) + strlen(client_sock.sun_path);
-#endif
-  // connect call
-  if (connect(main_socket_fd, reinterpret_cast<struct sockaddr *>(&client_sock), sockaddr_len) < 0) {
-    close(main_socket_fd);
-    main_socket_fd = -1;
-    goto ERROR; // connection is down
-  }
-  // -------- set up the event socket ------------------
-  // create a socket
-  event_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
-  if (event_socket_fd < 0) {
-    close(main_socket_fd); // close the other socket too!
-    main_socket_fd = -1;
-    goto ERROR; // ERROR - can't open socket
-  }
-  // setup Unix domain socket
-  memset(&client_event_sock, 0, sizeof(sockaddr_un));
-  client_event_sock.sun_family = AF_UNIX;
-  ink_strlcpy(client_event_sock.sun_path, event_socket_path, sizeof(client_event_sock.sun_path));
-#if defined(darwin) || defined(freebsd)
-  sockaddr_len = sizeof(sockaddr_un);
-#else
-  sockaddr_len = sizeof(client_event_sock.sun_family) + strlen(client_event_sock.sun_path);
-#endif
-  // connect call
-  if (connect(event_socket_fd, reinterpret_cast<struct sockaddr *>(&client_event_sock), sockaddr_len) < 0) {
-    close(event_socket_fd);
-    close(main_socket_fd);
-    event_socket_fd = -1;
-    main_socket_fd  = -1;
-    goto ERROR; // connection is down
-  }
-
-  return TS_ERR_OKAY;
-
-ERROR:
-  return TS_ERR_NET_ESTABLISH;
-}
-
-/***************************************************************************
- * disconnect
- *
- * purpose: disconnect from traffic server; closes sockets and resets their values
- * input: None
- * output: TS_ERR_FAIL, TS_ERR_OKAY
- * notes: doesn't do clean up - all cleanup should be done before here
- ***************************************************************************/
-TSMgmtError
-disconnect()
-{
-  int ret;
-
-  if (main_socket_fd > 0) {
-    ret            = close(main_socket_fd);
-    main_socket_fd = -1;
-    if (ret < 0) {
-      return TS_ERR_FAIL;
-    }
-  }
-
-  if (event_socket_fd > 0) {
-    ret             = close(event_socket_fd);
-    event_socket_fd = -1;
-    if (ret < 0) {
-      return TS_ERR_FAIL;
-    }
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/***************************************************************************
- * reconnect
- *
- * purpose: reconnects to TM (eg. when TM restarts); does all the necessary
- *          set up for reconnection
- * input: None
- * output: TS_ERR_FAIL, TS_ERR_OKAY
- * notes: necessary events for a new client-TM connection:
- * 1) get new socket_fd using old socket_path by calling connect()
- * 2) relaunch event_poll_thread_main with new socket_fd
- * 3) re-notify TM of all the client's registered callbacks by send msg
- ***************************************************************************/
-TSMgmtError
-reconnect()
-{
-  TSMgmtError err;
-
-  err = disconnect();
-  if (err != TS_ERR_OKAY) { // problem disconnecting
-    return err;
-  }
-
-  // use the socket_path that was called by remote client on first init
-  // use connect instead of TSInit() b/c if TM restarted, client-side tables
-  // would be recreated; just want to reconnect to same socket_path
-  err = ts_connect();
-  if (err != TS_ERR_OKAY) { // problem establishing connection
-    return err;
-  }
-
-  // relaunch a new event thread since socket_fd changed
-  if (0 == (ts_init_options & TS_MGMT_OPT_NO_EVENTS)) {
-    ink_thread_create(&ts_event_thread, event_poll_thread_main, &event_socket_fd, 0, 0, nullptr);
-    // re-register the callbacks on the TM side for this new client connection
-    if (remote_event_callbacks) {
-      err = send_register_all_callbacks(event_socket_fd, remote_event_callbacks);
-      if (err != TS_ERR_OKAY) { // problem establishing connection
-        return err;
-      }
-    }
-  } else {
-    ts_event_thread = ink_thread_null();
-  }
-
-  return TS_ERR_OKAY;
-}
-
-/***************************************************************************
- * reconnect_loop
- *
- * purpose: attempts to reconnect to TM (eg. when TM restarts) for the
- *          specified number of times
- * input:  num_attempts - number of reconnection attempts to try before quit
- * output: TS_ERR_OKAY - if successfully reconnected within num_attempts
- *         TS_ERR_xx - the reason the reconnection failed
- * notes:
- ***************************************************************************/
-TSMgmtError
-reconnect_loop(int num_attempts)
-{
-  int numTries    = 0;
-  TSMgmtError err = TS_ERR_FAIL;
-
-  while (numTries < num_attempts) {
-    numTries++;
-    err = reconnect();
-    if (err == TS_ERR_OKAY) {
-      return TS_ERR_OKAY; // successful connection
-    }
-    sleep(1); // to make it slower
-  }
-
-  return err; // unsuccessful connection after num_attempts
-}
-
-/*************************************************************************
- * connect_and_send
- *
- * purpose:
- * When sending a request, it's possible that the user had restarted
- * Traffic Manager. This means that the connection between TM and
- * the remote client has been broken, so the client needs to re-"connect"
- * to Traffic Manager. So, after "writing" to the socket in each
- * "send_xx_request" function, need to check if the TM socket has
- * been closed or not; the "write" function's errno will indicate if
- * the other end of the socket has been closed or not. If it is closed,
- * then need to try to re"connect", then resend the message request if
- * the "connect" was successful.
- * 1) try connect()
- * 2) if connect() success, then resend the request.
- * output: TS_ERR_NET_xx - connection problem or TS_ERR_OKAY
- * notes:
- * This function is basically called by the special "socket_write_conn" fn
- * which will call this fn if it tries to write to the socket and discovers
- * the local end of the socket is closed
- * Warning: system also sends a SIGPIPE error when try to write to socket
- * which is not open; which will by default terminate the process;
- * client needs to "ignore" the SIGPIPE signal
- **************************************************************************/
-static TSMgmtError
-main_socket_reconnect()
-{
-  TSMgmtError err;
-
-  // connects to TM and does all necessary event updates required
-  err = reconnect();
-  if (err != TS_ERR_OKAY) {
-    return err;
-  }
-
-  // makes sure the descriptor is writable
-  if (mgmt_write_timeout(main_socket_fd, MAX_TIME_WAIT, 0) <= 0) {
-    return TS_ERR_NET_TIMEOUT;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-static TSMgmtError
-socket_write_conn(int fd, const void *msg_buf, size_t bytes)
-{
-  size_t byte_wrote = 0;
-
-  // makes sure the descriptor is writable
-  if (mgmt_write_timeout(fd, MAX_TIME_WAIT, 0) <= 0) {
-    return TS_ERR_NET_TIMEOUT;
-  }
-
-  // write until we fulfill the number
-  while (byte_wrote < bytes) {
-    ssize_t ret = write(fd, static_cast<const char *>(msg_buf) + byte_wrote, bytes - byte_wrote);
-
-    if (ret == 0) {
-      return TS_ERR_NET_EOF;
-    }
-
-    if (ret < 0) {
-      if (mgmt_transient_error()) {
-        continue;
-      } else {
-        return TS_ERR_NET_WRITE;
-      }
-    }
-
-    // we are all good here
-    byte_wrote += ret;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-TSMgmtError
-mgmtapi_sender::send(void *msg, size_t msglen) const
-{
-  const unsigned tries = 5;
-  TSMgmtError err;
-
-  for (unsigned i = 0; i < tries; ++i) {
-    err = socket_write_conn(this->fd, msg, msglen);
-    if (err == TS_ERR_OKAY) {
-      return err;
-    }
-
-    // clean-up sockets
-    close(main_socket_fd);
-    close(event_socket_fd);
-    main_socket_fd  = -1;
-    event_socket_fd = -1;
-
-    err = main_socket_reconnect();
-    if (err != TS_ERR_OKAY) {
-      return err;
-    }
-  }
-
-  return TS_ERR_NET_ESTABLISH; // can't establish connection
-}
-
-/**********************************************************************
- * socket_test_thread
- *
- * purpose: continually polls to check if local end of socket connection
- *          is still open; this thread is created when the client calls
- *          Init() to initialize the API; and will not
- *          die until the client process dies
- * input: none
- * output: if other end is closed, it reconnects to TM
- * notes: uses the current main_socket_fd because the main_socket_fd could be
- *        in flux; basically it is possible that the client will reconnect
- *        from some other call, thus making the main_socket_fd actually
- *        valid when socket_test is called
- * reason: decided to create this "watcher" thread for the socket
- *         connection because if TM is restarted or the client process
- *         is started before the TM process, then the client will not
- *         be able to receive any event notifications until a "request"
- *         is issued. In order to prevent losing an event notifications
- *         that are called in between the time TM is restarted and
- *         client issues a first request, we just run this thread which
- *         will try to reconnect to TM if it is not already connected
- **********************************************************************/
-void *
-socket_test_thread(void *)
-{
-  // loop until client process dies
-  while (true) {
-    if (main_socket_fd == -1 || !socket_test(main_socket_fd)) {
-      // ASSUMES that in between the time the socket_test is made
-      // and this reconnect call is made, the main_socket_fd remains
-      // the same (eg. no one else called reconnect to TM successfully!!
-      // WHAT IF in between this time, the client had issued a request
-      // calling socket_write_conn which then calls reconnect(); then
-      // reconnect will return an "ALREADY CONNECTED" error when it
-      // tries to connect, and on the next loop iteration, the socket_test
-      // will actually pass because main_socket_fd is valid!!
-      reconnect();
-    }
-
-    sleep(5);
-  }
-
-  ink_thread_exit(nullptr);
-  return nullptr;
-}
-
-/**********************************************************************
- * MARSHALL REQUESTS
- **********************************************************************/
-
-/*------ events -------------------------------------------------------*/
-
-/**********************************************************************
- * send_register_all_callbacks
- *
- * purpose: determines all events which have at least one callback registered
- *          and sends message to notify TM that this client has a callback
- *          registered for each event
- * input: None
- * output: return TS_ERR_OKAY only if ALL events sent okay
- * 1) get list of all events with callbacks
- * 2) for each event, send a EVENT_REG_CALLBACK message
- **********************************************************************/
-TSMgmtError
-send_register_all_callbacks(int fd, CallbackTable *cb_table)
-{
-  LLQ *events_with_cb;
-  TSMgmtError err, send_err = TS_ERR_FAIL;
-  bool no_errors = true; // set to false if one send is not okay
-
-  events_with_cb = get_events_with_callbacks(cb_table);
-  // need to check that the list has all the events registered
-  if (!events_with_cb) { // all events have registered callback
-    OpType optype                 = OpType::EVENT_REG_CALLBACK;
-    MgmtMarshallString event_name = nullptr;
-
-    err = MGMTAPI_SEND_MESSAGE(fd, OpType::EVENT_REG_CALLBACK, &optype, &event_name);
-    if (err != TS_ERR_OKAY) {
-      return err;
-    }
-  } else {
-    int num_events = queue_len(events_with_cb);
-    // iterate through the LLQ and send request for each event
-    for (int i = 0; i < num_events; i++) {
-      OpType optype                 = OpType::EVENT_REG_CALLBACK;
-      MgmtMarshallInt event_id      = *static_cast<int *>(dequeue(events_with_cb));
-      MgmtMarshallString event_name = get_event_name(event_id);
-
-      if (event_name) {
-        err = MGMTAPI_SEND_MESSAGE(fd, OpType::EVENT_REG_CALLBACK, &optype, &event_name);
-        ats_free(event_name); // free memory
-        if (err != TS_ERR_OKAY) {
-          send_err  = err; // save the type of send error
-          no_errors = false;
-        }
-      }
-      // REMEMBER: WON"T GET A REPLY from TM side!
-    }
-  }
-
-  if (events_with_cb) {
-    delete_queue(events_with_cb);
-  }
-
-  if (no_errors) {
-    return TS_ERR_OKAY;
-  } else {
-    return send_err;
-  }
-}
-
-/**********************************************************************
- * send_unregister_all_callbacks
- *
- * purpose: determines all events which have no callback registered
- *          and sends message to notify TM that this client has no
- *          callbacks registered for that event
- * input: None
- * output: TS_ERR_OKAY only if all send requests are okay
- **********************************************************************/
-TSMgmtError
-send_unregister_all_callbacks(int fd, CallbackTable *cb_table)
-{
-  LLQ *events_with_cb; // list of events with at least one callback
-  int reg_callback[NUM_EVENTS];
-  TSMgmtError err, send_err = TS_ERR_FAIL;
-  bool no_errors = true; // set to false if at least one send fails
-
-  // init array so that all events don't have any callbacks
-  for (int &i : reg_callback) {
-    i = 0;
-  }
-
-  events_with_cb = get_events_with_callbacks(cb_table);
-  if (!events_with_cb) { // all events have a registered callback
-    return TS_ERR_OKAY;
-  } else {
-    int num_events = queue_len(events_with_cb);
-    // iterate through the LLQ and mark events that have a callback
-    for (int i = 0; i < num_events; i++) {
-      int event_id           = *static_cast<int *>(dequeue(events_with_cb));
-      reg_callback[event_id] = 1; // mark the event as having a callback
-    }
-    delete_queue(events_with_cb);
-  }
-
-  // send message to TM to mark unregister
-  for (int k = 0; k < NUM_EVENTS; k++) {
-    if (reg_callback[k] == 0) { // event has no registered callbacks
-      OpType optype                 = OpType::EVENT_UNREG_CALLBACK;
-      MgmtMarshallString event_name = get_event_name(k);
-
-      err = MGMTAPI_SEND_MESSAGE(fd, OpType::EVENT_UNREG_CALLBACK, &optype, &event_name);
-      ats_free(event_name);
-      if (err != TS_ERR_OKAY) {
-        send_err  = err; // save the type of the sending error
-        no_errors = false;
-      }
-      // REMEMBER: WON"T GET A REPLY!
-      // only the event_poll_thread_main does any reading of the event_socket;
-      // so DO NOT parse reply b/c a reply won't be sent
-    }
-  }
-
-  if (no_errors) {
-    return TS_ERR_OKAY;
-  } else {
-    return send_err;
-  }
-}
-
-/**********************************************************************
- * UNMARSHAL REPLIES
- **********************************************************************/
-
-TSMgmtError
-parse_generic_response(OpType optype, int fd)
-{
-  TSMgmtError err;
-  MgmtMarshallInt ival;
-  MgmtMarshallData data = {nullptr, 0};
-
-  err = recv_mgmt_message(fd, data);
-  if (err != TS_ERR_OKAY) {
-    return err;
-  }
-
-  err = recv_mgmt_response(data.ptr, data.len, optype, &ival);
-  ats_free(data.ptr);
-
-  if (err != TS_ERR_OKAY) {
-    return err;
-  }
-
-  return static_cast<TSMgmtError>(ival);
-}
-
-/**********************************************************************
- * event_poll_thread_main
- *
- * purpose: thread listens on the client's event socket connection;
- *          only reads from the event_socket connection and
- *          processes EVENT_NOTIFY messages; each time client
- *          makes new event-socket connection to TM, must launch
- *          a new event_poll_thread_main thread
- * input:   arg - contains the socket_fd to listen on
- * output:  NULL - if error
- * notes:   each time the client's socket connection to TM is reset
- *          a new thread will be launched as old one dies; there are
- *          only two places where a new thread is created:
- *          1) when client first connects (TSInit call)
- *          2) client reconnects() due to a TM restart
- * Uses blocking socket; so blocks until receives an event notification.
- * Shouldn't need to use select since only waiting for a notification
- * message from event_callback_main thread!
- **********************************************************************/
-void *
-event_poll_thread_main(void *arg)
-{
-  int sock_fd;
-
-  sock_fd = *(static_cast<int *>(arg)); // should be same as event_socket_fd
-
-  // the sock_fd is going to be the one we listen for events on
-  while (true) {
-    TSMgmtError ret;
-    TSMgmtEvent *event = nullptr;
-
-    MgmtMarshallData reply = {nullptr, 0};
-    OpType optype;
-    MgmtMarshallString name = nullptr;
-    MgmtMarshallString desc = nullptr;
-
-    // possible sock_fd is invalid if TM restarts and client reconnects
-    if (sock_fd < 0) {
-      break;
-    }
-
-    // Just wait until we get an event or error. The 0 return from select(2)
-    // means we timed out ...
-    if (mgmt_read_timeout(main_socket_fd, MAX_TIME_WAIT, 0) == 0) {
-      continue;
-    }
-
-    ret = recv_mgmt_message(main_socket_fd, reply);
-    if (ret != TS_ERR_OKAY) {
-      break;
-    }
-
-    ret = recv_mgmt_request(reply.ptr, reply.len, OpType::EVENT_NOTIFY, &optype, &name, &desc);
-    ats_free(reply.ptr);
-
-    if (ret != TS_ERR_OKAY) {
-      ats_free(name);
-      ats_free(desc);
-      break;
-    }
-
-    ink_assert(optype == OpType::EVENT_NOTIFY);
-
-    // The new event takes ownership of the message strings.
-    event              = TSEventCreate();
-    event->name        = name;
-    event->id          = get_event_id(name);
-    event->description = desc;
-
-    // got event notice; spawn new thread to handle the event's callback functions
-    ink_thread_create(nullptr, event_callback_thread, (void *)event, 0, 0, nullptr);
-  }
-
-  ink_thread_exit(nullptr);
-  return nullptr;
-}
-
-/**********************************************************************
- * event_callback_thread
- *
- * purpose: Given an event, determines and calls the registered cb functions
- *          in the CallbackTable for remote events
- * input: arg - should be an TSMgmtEvent with the event info sent from TM msg
- * output: returns when done calling all the callbacks
- * notes: None
- **********************************************************************/
-static void *
-event_callback_thread(void *arg)
-{
-  TSMgmtEvent *event_notice;
-  EventCallbackT *event_cb;
-  int index;
-
-  event_notice = static_cast<TSMgmtEvent *>(arg);
-  index        = event_notice->id;
-  LLQ *func_q; // list of callback functions need to call
-
-  func_q = create_queue();
-  if (!func_q) {
-    TSEventDestroy(event_notice);
-    return nullptr;
-  }
-
-  // obtain lock
-  ink_mutex_acquire(&remote_event_callbacks->event_callback_lock);
-
-  TSEventSignalFunc cb;
-
-  // check if we have functions to call
-  if (remote_event_callbacks->event_callback_l[index] && (!queue_is_empty(remote_event_callbacks->event_callback_l[index]))) {
-    int queue_depth = queue_len(remote_event_callbacks->event_callback_l[index]);
-
-    for (int i = 0; i < queue_depth; i++) {
-      event_cb = static_cast<EventCallbackT *>(dequeue(remote_event_callbacks->event_callback_l[index]));
-      cb       = event_cb->func;
-      enqueue(remote_event_callbacks->event_callback_l[index], event_cb);
-      enqueue(func_q, reinterpret_cast<void *>(cb)); // add callback function only to list
-    }
-  }
-  // release lock
-  ink_mutex_release(&remote_event_callbacks->event_callback_lock);
-
-  // execute the callback function
-  while (!queue_is_empty(func_q)) {
-    cb = reinterpret_cast<TSEventSignalFunc>(dequeue(func_q));
-    (*cb)(event_notice->name, event_notice->description, event_notice->priority, nullptr);
-  }
-
-  // clean up event notice
-  TSEventDestroy(event_notice);
-  delete_queue(func_q);
-
-  // all done!
-  ink_thread_exit(nullptr);
-  return nullptr;
-}
diff --git a/mgmt/api/NetworkUtilsRemote.h b/mgmt/api/NetworkUtilsRemote.h
deleted file mode 100644
index f8413dd..0000000
--- a/mgmt/api/NetworkUtilsRemote.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: NetworkUtilsRemote.h
- * Purpose: This file contains functions used by remote api client to
- *          marshal requests to TM and unmarshal replies from TM.
- *          Also contains functions used to store information specific
- *          to a remote client connection.
- * Created: 8/9/00
- * Created by: lant
- *
- ***************************************************************************/
-
-#pragma once
-
-#include "mgmtapi.h"
-#include "ts/apidefs.h"
-#include "NetworkMessage.h"
-#include "EventCallback.h"
-
-extern int main_socket_fd;
-extern int event_socket_fd;
-extern CallbackTable *remote_event_callbacks;
-
-// From CoreAPIRemote.cc
-extern ink_thread ts_event_thread;
-extern TSInitOptionT ts_init_options;
-
-/**********************************************************************
- * Socket Helper Functions
- **********************************************************************/
-void set_socket_paths(const char *path);
-
-/* The following functions are specific for a client connection; uses
- * the client connection information stored in the variables in
- * NetworkUtilsRemote.cc
- */
-TSMgmtError
-ts_connect(); /* TODO: update documentation, Renamed due to conflict with connect() in <sys/socket.h> on some platforms*/
-TSMgmtError disconnect();
-TSMgmtError reconnect();
-TSMgmtError reconnect_loop(int num_attempts);
-
-void *socket_test_thread(void *arg);
-void *event_poll_thread_main(void *arg);
-
-struct mgmtapi_sender : public mgmt_message_sender {
-  explicit mgmtapi_sender(int _fd) : fd(_fd) {}
-  TSMgmtError send(void *msg, size_t msglen) const override;
-  bool
-  is_connected() const override
-  {
-    return fd != ts::NO_FD;
-  }
-
-  int fd;
-};
-
-#define MGMTAPI_SEND_MESSAGE(fd, optype, ...) send_mgmt_request(mgmtapi_sender(fd), (optype), __VA_ARGS__)
-
-#define MGMTAPI_MGMT_SOCKET_NAME "mgmtapi.sock"
-#define MGMTAPI_EVENT_SOCKET_NAME "eventapi.sock"
-
-/*****************************************************************************
- * Marshalling (create requests)
- *****************************************************************************/
-
-TSMgmtError send_register_all_callbacks(int fd, CallbackTable *cb_table);
-TSMgmtError send_unregister_all_callbacks(int fd, CallbackTable *cb_table);
-
-/*****************************************************************************
- * Un-marshalling (parse responses)
- *****************************************************************************/
-TSMgmtError parse_generic_response(OpType optype, int fd);
diff --git a/mgmt/api/TSControlMain.cc b/mgmt/api/TSControlMain.cc
deleted file mode 100644
index 74cc778..0000000
--- a/mgmt/api/TSControlMain.cc
+++ /dev/null
@@ -1,1091 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: TSControlMain.cc
- * Purpose: The main section for traffic server that handles all the requests
- *          from the user.
- * Created: 01/08/01
- * Created by: Stephanie Song
- *
- ***************************************************************************/
-
-#include "mgmtapi.h"
-#include "tscore/ink_platform.h"
-#include "tscore/ink_sock.h"
-#include "LocalManager.h"
-#include "MgmtUtils.h"
-#include "MgmtSocket.h"
-#include "NetworkMessage.h"
-#include "TSControlMain.h"
-#include "CoreAPI.h"
-#include "CoreAPIShared.h"
-#include "NetworkUtilsLocal.h"
-
-#include <unordered_map>
-
-#define TIMEOUT_SECS 1 // the num secs for select timeout
-
-static std::unordered_map<int, ClientT *> accepted_con; // a list of all accepted client connections
-
-static TSMgmtError handle_control_message(int fd, void *msg, size_t msglen);
-
-/*********************************************************************
- * create_client
- *
- * purpose: creates a new ClientT and return pointer to it
- * input: None
- * output: ClientT
- * note: created for each accepted client connection
- *********************************************************************/
-static ClientT *
-create_client()
-{
-  ClientT *ele = static_cast<ClientT *>(ats_malloc(sizeof(ClientT)));
-
-  ele->adr = static_cast<struct sockaddr *>(ats_malloc(sizeof(struct sockaddr)));
-  return ele;
-}
-
-/*********************************************************************
- * delete_client
- *
- * purpose: frees dynamic memory allocated for a ClientT
- * input: client - the ClientT to free
- * output:
- *********************************************************************/
-static void
-delete_client(ClientT *client)
-{
-  if (client) {
-    ats_free(client->adr);
-    ats_free(client);
-  }
-  return;
-}
-
-/*********************************************************************
- * remove_client
- *
- * purpose: removes the ClientT from the specified hashtable; includes
- *          removing the binding and freeing the ClientT
- * input: client - the ClientT to remove
- * output:
- *********************************************************************/
-static void
-remove_client(ClientT *client, std::unordered_map<int, ClientT *> &table)
-{
-  // close client socket
-  close_socket(client->fd);
-
-  // remove client binding from hash table
-  table.erase(client->fd);
-
-  // free ClientT
-  delete_client(client);
-
-  return;
-}
-
-/*********************************************************************
- * ts_ctrl_main
- *
- * This function is run as a thread in WebIntrMain.cc that listens on a
- * specified socket. It loops until Traffic Manager dies.
- * In the loop, it just listens on a socket, ready to accept any connections,
- * until receives a request from the remote API client. Parse the request
- * to determine which CoreAPI call to make.
- *********************************************************************/
-void *
-ts_ctrl_main(void *arg)
-{
-  int ret;
-  int *socket_fd;
-  int con_socket_fd; // main socket for listening to new connections
-
-  socket_fd     = static_cast<int *>(arg);
-  con_socket_fd = *socket_fd;
-
-  // now we can start listening, accepting connections and servicing requests
-  int new_con_fd; // new socket fd when accept connection
-
-  fd_set selectFDs;      // for select call
-  ClientT *client_entry; // an entry of fd to alarms mapping
-  struct timeval timeout;
-
-  // loops until TM dies; waits for and processes requests from clients
-  while (true) {
-    // LINUX: to prevent hard-spin of CPU,  reset timeout on each loop
-    timeout.tv_sec  = TIMEOUT_SECS;
-    timeout.tv_usec = 0;
-
-    FD_ZERO(&selectFDs);
-
-    if (con_socket_fd >= 0) {
-      FD_SET(con_socket_fd, &selectFDs);
-      // Debug("ts_main", "[ts_ctrl_main] add fd %d to select set", con_socket_fd);
-    }
-
-    for (auto &&it : accepted_con) {
-      client_entry = it.second;
-      if (client_entry->fd >= 0) { // add fd to select set
-        FD_SET(client_entry->fd, &selectFDs);
-        Debug("ts_main", "[ts_ctrl_main] add fd %d to select set", client_entry->fd);
-      }
-    }
-
-    // select call - timeout is set so we can check events at regular intervals
-    int fds_ready = mgmt_select(FD_SETSIZE, &selectFDs, (fd_set *)nullptr, (fd_set *)nullptr, &timeout);
-
-    // check if have any connections or requests
-    if (fds_ready > 0) {
-      // first check for connections!
-      if (con_socket_fd >= 0 && FD_ISSET(con_socket_fd, &selectFDs)) {
-        fds_ready--;
-
-        // create a new instance to store client connection info
-        ClientT *new_client_con = create_client();
-        if (!new_client_con) {
-          // return TS_ERR_SYS_CALL; WHAT TO DO? just keep going
-          Debug("ts_main", "[ts_ctrl_main] can't allocate new ClientT");
-        } else { // accept connection
-          socklen_t addr_len = (sizeof(struct sockaddr));
-          new_con_fd         = mgmt_accept(con_socket_fd, new_client_con->adr, &addr_len);
-          new_client_con->fd = new_con_fd;
-          accepted_con.emplace(new_client_con->fd, new_client_con);
-          Debug("ts_main", "[ts_ctrl_main] Add new client connection");
-        }
-      } // end if(new_con_fd >= 0 && FD_ISSET(new_con_fd, &selectFDs))
-
-      // some other file descriptor; for each one, service request
-      if (fds_ready > 0) { // RECEIVED A REQUEST from remote API client
-        // see if there are more fd to set - iterate through all entries in hash table
-
-        for (auto it = accepted_con.begin(); it != accepted_con.end();) {
-          Debug("ts_main", "[ts_ctrl_main] We have a remote client request!");
-          client_entry = it->second;
-          ++it; // prevent the breaking of remove_client
-          // got information; check
-          if (client_entry->fd && FD_ISSET(client_entry->fd, &selectFDs)) {
-            void *req = nullptr;
-            size_t reqlen;
-
-            ret = preprocess_msg(client_entry->fd, &req, &reqlen);
-            if (ret == TS_ERR_NET_READ || ret == TS_ERR_NET_EOF) {
-              // occurs when remote API client terminates connection
-              Debug("ts_main", "[ts_ctrl_main] ERROR: preprocess_msg - remove client %d ", client_entry->fd);
-              remove_client(client_entry, accepted_con);
-              continue;
-            }
-
-            ret = handle_control_message(client_entry->fd, req, reqlen);
-            ats_free(req);
-
-            if (ret != TS_ERR_OKAY) {
-              Debug("ts_main", "[ts_ctrl_main] ERROR: sending response for message (%d)", ret);
-              // XXX this doesn't actually send a error response ...
-              remove_client(client_entry, accepted_con);
-              continue;
-            }
-
-          } // end if(client_entry->fd && FD_ISSET(client_entry->fd, &selectFDs))
-        }   // end for (auto it = accepted_con.begin(); it != accepted_con.end();)
-      }     // end if (fds_ready > 0)
-
-    } // end if (fds_ready > 0)
-
-  } // end while (1)
-
-  // if we get here something's wrong, just clean up
-  Debug("ts_main", "[ts_ctrl_main] CLOSING AND SHUTTING DOWN OPERATIONS");
-  close_socket(con_socket_fd);
-
-  for (auto &&it : accepted_con) {
-    client_entry = it.second;
-    if (client_entry->fd >= 0) {
-      close_socket(client_entry->fd); // close socket
-    }
-    accepted_con.erase(client_entry->fd);
-    delete_client(client_entry); // free ClientT
-  }
-  // all entries should be removed and freed already
-  accepted_con.clear();
-
-  ink_thread_exit(nullptr);
-  return nullptr;
-}
-
-/*-------------------------------------------------------------------------
-                             HANDLER FUNCTIONS
- --------------------------------------------------------------------------*/
-/* NOTE: all the handle_xx functions basically, take the request, parse it,
- * and send a reply back to the remote client. So even if error occurs,
- * each handler functions MUST SEND A REPLY BACK!!
- */
-
-static TSMgmtError
-marshall_rec_data(RecDataT rec_type, const RecData &rec_data, MgmtMarshallData &data)
-{
-  switch (rec_type) {
-  case RECD_INT:
-    data.ptr = const_cast<RecInt *>(&rec_data.rec_int);
-    data.len = sizeof(TSInt);
-    break;
-  case RECD_COUNTER:
-    data.ptr = const_cast<RecCounter *>(&rec_data.rec_counter);
-    data.len = sizeof(TSCounter);
-    break;
-  case RECD_FLOAT:
-    data.ptr = const_cast<RecFloat *>(&rec_data.rec_float);
-    data.len = sizeof(TSFloat);
-    break;
-  case RECD_STRING:
-    // Make sure to send the NULL in the string value response.
-    if (rec_data.rec_string) {
-      data.ptr = rec_data.rec_string;
-      data.len = strlen(rec_data.rec_string) + 1;
-    } else {
-      data.ptr = (void *)"NULL";
-      data.len = countof("NULL");
-    }
-    break;
-  default: // invalid record type
-    return TS_ERR_FAIL;
-  }
-
-  return TS_ERR_OKAY;
-}
-
-static TSMgmtError
-send_record_get_response(int fd, const RecRecord *rec)
-{
-  MgmtMarshallInt err = TS_ERR_OKAY;
-  MgmtMarshallInt type;
-  MgmtMarshallInt rclass;
-  MgmtMarshallString name;
-  MgmtMarshallData value = {nullptr, 0};
-
-  if (rec) {
-    type   = rec->data_type;
-    rclass = rec->rec_type;
-    name   = const_cast<MgmtMarshallString>(rec->name);
-  } else {
-    type   = RECD_NULL;
-    rclass = RECT_NULL;
-    name   = nullptr;
-  }
-
-  switch (type) {
-  case RECD_INT:
-    type      = TS_REC_INT;
-    value.ptr = (void *)&rec->data.rec_int;
-    value.len = sizeof(RecInt);
-    break;
-  case RECD_COUNTER:
-    type      = TS_REC_COUNTER;
-    value.ptr = (void *)&rec->data.rec_counter;
-    value.len = sizeof(RecCounter);
-    break;
-  case RECD_FLOAT:
-    type      = TS_REC_FLOAT;
-    value.ptr = (void *)&rec->data.rec_float;
-    value.len = sizeof(RecFloat);
-    break;
-  case RECD_STRING:
-    // For NULL string parameters, send the literal "NULL" to match the behavior of MgmtRecordGet(). Make sure to send
-    // the trailing NULL.
-    type = TS_REC_STRING;
-    if (rec->data.rec_string) {
-      value.ptr = rec->data.rec_string;
-      value.len = strlen(rec->data.rec_string) + 1;
-    } else {
-      value.ptr = const_cast<char *>("NULL");
-      value.len = countof("NULL");
-    }
-    break;
-  default:
-    type = TS_REC_UNDEFINED;
-    break; // skip it
-  }
-
-  return send_mgmt_response(fd, OpType::RECORD_GET, &err, &rclass, &type, &name, &value);
-}
-
-/**************************************************************************
- * handle_record_get
- *
- * purpose: handles requests to retrieve values of certain variables
- *          in TM. (see local/TSCtrlFunc.cc)
- * input: socket information
- *        req - the msg sent (should = record name to get)
- * output: SUCC or ERR
- * note:
- *************************************************************************/
-static void
-send_record_get(const RecRecord *rec, void *edata)
-{
-  int *fd = static_cast<int *>(edata);
-  *fd     = send_record_get_response(*fd, rec);
-}
-
-static TSMgmtError
-handle_record_get(int fd, void *req, size_t reqlen)
-{
-  TSMgmtError ret;
-  MgmtMarshallInt optype;
-  MgmtMarshallString name;
-
-  int fderr = fd; // [in,out] variable for the fd and error
-
-  ret = recv_mgmt_request(req, reqlen, OpType::RECORD_GET, &optype, &name);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  if (strlen(name) == 0) {
-    ret = TS_ERR_PARAMS;
-    goto done;
-  }
-
-  fderr = fd;
-  if (RecLookupRecord(name, send_record_get, &fderr) != REC_ERR_OKAY) {
-    ret = TS_ERR_PARAMS;
-    goto done;
-  }
-
-  // If the lookup succeeded, the final error is in "fderr".
-  if (ret == TS_ERR_OKAY) {
-    ret = static_cast<TSMgmtError>(fderr);
-  }
-
-done:
-  ats_free(name);
-  return ret;
-}
-
-struct record_match_state {
-  TSMgmtError err;
-  int fd;
-};
-
-static void
-send_record_match(const RecRecord *rec, void *edata)
-{
-  record_match_state *match = static_cast<record_match_state *>(edata);
-
-  if (match->err != TS_ERR_OKAY) {
-    return;
-  }
-
-  match->err = send_record_get_response(match->fd, rec);
-}
-
-static TSMgmtError
-handle_record_match(int fd, void *req, size_t reqlen)
-{
-  TSMgmtError ret;
-  record_match_state match;
-  MgmtMarshallInt optype;
-  MgmtMarshallString name;
-
-  ret = recv_mgmt_request(req, reqlen, OpType::RECORD_MATCH_GET, &optype, &name);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  if (strlen(name) == 0) {
-    ats_free(name);
-    return TS_ERR_FAIL;
-  }
-
-  match.err = TS_ERR_OKAY;
-  match.fd  = fd;
-
-  if (RecLookupMatchingRecords(RECT_ALL, name, send_record_match, &match) != REC_ERR_OKAY) {
-    ats_free(name);
-    return TS_ERR_FAIL;
-  }
-
-  ats_free(name);
-
-  // If successful, send a list terminator.
-  if (match.err == TS_ERR_OKAY) {
-    return send_record_get_response(fd, nullptr);
-  }
-
-  return match.err;
-}
-
-/**************************************************************************
- * handle_record_set
- *
- * purpose: handles a set request sent by the client
- * output: SUCC or ERR
- * note: request format = <record name>DELIMITER<record_value>
- *************************************************************************/
-static TSMgmtError
-handle_record_set(int fd, void *req, size_t reqlen)
-{
-  TSMgmtError ret;
-  TSActionNeedT action = TS_ACTION_UNDEFINED;
-  MgmtMarshallInt optype;
-  MgmtMarshallString name  = nullptr;
-  MgmtMarshallString value = nullptr;
-
-  ret = recv_mgmt_request(req, reqlen, OpType::RECORD_SET, &optype, &name, &value);
-  if (ret != TS_ERR_OKAY) {
-    ret = TS_ERR_FAIL;
-    goto fail;
-  }
-
-  if (strlen(name) == 0) {
-    ret = TS_ERR_PARAMS;
-    goto fail;
-  }
-
-  // call CoreAPI call on Traffic Manager side
-  ret = MgmtRecordSet(name, value, &action);
-
-fail:
-  ats_free(name);
-  ats_free(value);
-
-  MgmtMarshallInt err = ret;
-  MgmtMarshallInt act = action;
-  return send_mgmt_response(fd, OpType::RECORD_SET, &err, &act);
-}
-
-/**************************************************************************
- * handle_proxy_state_get
- *
- * purpose: handles request to get the state of the proxy (TS)
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_proxy_state_get(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallInt err;
-  MgmtMarshallInt state = TS_PROXY_UNDEFINED;
-
-  err = recv_mgmt_request(req, reqlen, OpType::PROXY_STATE_GET, &optype);
-  if (err == TS_ERR_OKAY) {
-    state = ProxyStateGet();
-  }
-
-  return send_mgmt_response(fd, OpType::PROXY_STATE_GET, &err, &state);
-}
-
-/**************************************************************************
- * handle_proxy_state_set
- *
- * purpose: handles the request to set the state of the proxy (TS)
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_proxy_state_set(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallInt state;
-  MgmtMarshallInt clear;
-
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::PROXY_STATE_SET, &optype, &state, &clear);
-  if (err != TS_ERR_OKAY) {
-    return send_mgmt_response(fd, OpType::PROXY_STATE_SET, &err);
-  }
-
-  err = ProxyStateSet(static_cast<TSProxyStateT>(state), static_cast<TSCacheClearT>(clear));
-  return send_mgmt_response(fd, OpType::PROXY_STATE_SET, &err);
-}
-
-/**************************************************************************
- * handle_reconfigure
- *
- * purpose: handles request to reread the config files
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_reconfigure(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt err;
-  MgmtMarshallInt optype;
-
-  err = recv_mgmt_request(req, reqlen, OpType::RECONFIGURE, &optype);
-  if (err == TS_ERR_OKAY) {
-    err = Reconfigure();
-  }
-
-  return send_mgmt_response(fd, OpType::RECONFIGURE, &err);
-}
-
-/**************************************************************************
- * handle_restart
- *
- * purpose: handles request to restart TM and TS
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_restart(int fd, void *req, size_t reqlen)
-{
-  OpType optype;
-  MgmtMarshallInt options;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::RESTART, &optype, &options);
-  if (err == TS_ERR_OKAY) {
-    switch (optype) {
-    case OpType::BOUNCE:
-      err = Bounce(options);
-      break;
-    case OpType::RESTART:
-      err = Restart(options);
-      break;
-    default:
-      err = TS_ERR_PARAMS;
-      break;
-    }
-  }
-
-  return send_mgmt_response(fd, OpType::RESTART, &err);
-}
-
-/**************************************************************************
- * handle_stop
- *
- * purpose: handles request to stop TS
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_stop(int fd, void *req, size_t reqlen)
-{
-  OpType optype;
-  MgmtMarshallInt options;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::STOP, &optype, &options);
-  if (err == TS_ERR_OKAY) {
-    err = Stop(options);
-  }
-
-  return send_mgmt_response(fd, OpType::STOP, &err);
-}
-
-/**************************************************************************
- * handle_drain
- *
- * purpose: handles request to drain TS
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_drain(int fd, void *req, size_t reqlen)
-{
-  OpType optype;
-  MgmtMarshallInt options;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::DRAIN, &optype, &options);
-  if (err == TS_ERR_OKAY) {
-    err = Drain(options);
-  }
-
-  return send_mgmt_response(fd, OpType::DRAIN, &err);
-}
-
-/**************************************************************************
- * handle_storage_device_cmd_offline
- *
- * purpose: handle storage offline command.
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_storage_device_cmd_offline(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallString name = nullptr;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::STORAGE_DEVICE_CMD_OFFLINE, &optype, &name);
-  if (err == TS_ERR_OKAY) {
-    // forward to server
-    lmgmt->signalEvent(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, name);
-  }
-
-  return send_mgmt_response(fd, OpType::STORAGE_DEVICE_CMD_OFFLINE, &err);
-}
-
-/**************************************************************************
- * handle_event_resolve
- *
- * purpose: handles request to resolve an event
- * output: TS_ERR_xx
- * note: the req should be the event name
- *************************************************************************/
-static TSMgmtError
-handle_event_resolve(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallString name = nullptr;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::EVENT_RESOLVE, &optype, &name);
-  if (err == TS_ERR_OKAY) {
-    err = EventResolve(name);
-  }
-
-  ats_free(name);
-  return send_mgmt_response(fd, OpType::EVENT_RESOLVE, &err);
-}
-
-/**************************************************************************
- * handle_event_get_mlt
- *
- * purpose: handles request to get list of active events
- * output: TS_ERR_xx
- * note: the req should be the event name
- *************************************************************************/
-static TSMgmtError
-handle_event_get_mlt(int fd, void *req, size_t reqlen)
-{
-  LLQ *event_list = create_queue();
-  char buf[MAX_BUF_SIZE];
-  char *event_name;
-  int buf_pos = 0;
-
-  MgmtMarshallInt optype;
-  MgmtMarshallInt err;
-  MgmtMarshallString list = nullptr;
-
-  err = recv_mgmt_request(req, reqlen, OpType::EVENT_GET_MLT, &optype);
-  if (err != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  // call CoreAPI call on Traffic Manager side; req == event_name
-  err = ActiveEventGetMlt(event_list);
-  if (err != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  // iterate through list and put into a delimited string list
-  memset(buf, 0, MAX_BUF_SIZE);
-  while (!queue_is_empty(event_list)) {
-    event_name = static_cast<char *>(dequeue(event_list));
-    if (event_name) {
-      snprintf(buf + buf_pos, (MAX_BUF_SIZE - buf_pos), "%s%c", event_name, REMOTE_DELIM);
-      buf_pos += (strlen(event_name) + 1);
-      ats_free(event_name); // free the llq entry
-    }
-  }
-  buf[buf_pos] = '\0'; // end the string
-
-  // Point the send list to the filled buffer.
-  list = buf;
-
-done:
-  delete_queue(event_list);
-  return send_mgmt_response(fd, OpType::EVENT_GET_MLT, &err, &list);
-}
-
-/**************************************************************************
- * handle_event_active
- *
- * purpose: handles request to resolve an event
- * output: TS_ERR_xx
- * note: the req should be the event name
- *************************************************************************/
-static TSMgmtError
-handle_event_active(int fd, void *req, size_t reqlen)
-{
-  bool active;
-  MgmtMarshallInt optype;
-  MgmtMarshallString name = nullptr;
-
-  MgmtMarshallInt err;
-  MgmtMarshallInt bval = 0;
-
-  err = recv_mgmt_request(req, reqlen, OpType::EVENT_ACTIVE, &optype, &name);
-  if (err != TS_ERR_OKAY) {
-    goto done;
-  }
-
-  if (strlen(name) == 0) {
-    err = TS_ERR_PARAMS;
-    goto done;
-  }
-
-  err = EventIsActive(name, &active);
-  if (err == TS_ERR_OKAY) {
-    bval = active ? 1 : 0;
-  }
-
-done:
-  ats_free(name);
-  return send_mgmt_response(fd, OpType::EVENT_ACTIVE, &err, &bval);
-}
-
-/**************************************************************************
- * handle_stats_reset
- *
- * purpose: handles request to reset statistics to default values
- * output: TS_ERR_xx
- *************************************************************************/
-static TSMgmtError
-handle_stats_reset(int fd, void *req, size_t reqlen)
-{
-  OpType optype;
-  MgmtMarshallString name = nullptr;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::STATS_RESET_NODE, &optype, &name);
-  if (err == TS_ERR_OKAY) {
-    err = StatsReset(name);
-  }
-
-  ats_free(name);
-  return send_mgmt_response(fd, optype, &err);
-}
-
-/**************************************************************************
- * handle_host_status_up
- *
- * purpose: handles request to reset statistics to default values
- * output: TS_ERR_xx
- *************************************************************************/
-static TSMgmtError
-handle_host_status_up(int fd, void *req, size_t reqlen)
-{
-  OpType optype;
-  MgmtMarshallString name   = nullptr;
-  MgmtMarshallString reason = nullptr;
-  MgmtMarshallInt err;
-  MgmtMarshallInt down_time;
-
-  err = recv_mgmt_request(req, reqlen, OpType::HOST_STATUS_UP, &optype, &name, &reason, &down_time);
-  if (err == TS_ERR_OKAY) {
-    lmgmt->signalEvent(MGMT_EVENT_HOST_STATUS_UP, static_cast<char *>(req), reqlen);
-  }
-
-  ats_free(name);
-  return send_mgmt_response(fd, optype, &err);
-}
-
-/**************************************************************************
- * handle_host_status_down
- *
- * purpose: handles request to reset statistics to default values
- * output: TS_ERR_xx
- *************************************************************************/
-static TSMgmtError
-handle_host_status_down(int fd, void *req, size_t reqlen)
-{
-  OpType optype;
-  MgmtMarshallString name   = nullptr;
-  MgmtMarshallString reason = nullptr;
-  MgmtMarshallInt err;
-  MgmtMarshallInt down_time;
-
-  err = recv_mgmt_request(req, reqlen, OpType::HOST_STATUS_DOWN, &optype, &name, &reason, &down_time);
-  if (err == TS_ERR_OKAY) {
-    lmgmt->signalEvent(MGMT_EVENT_HOST_STATUS_DOWN, static_cast<char *>(req), reqlen);
-  }
-
-  ats_free(name);
-  return send_mgmt_response(fd, optype, &err);
-}
-/**************************************************************************
- * handle_api_ping
- *
- * purpose: handles the API_PING message that is sent by API clients to keep
- *    the management socket alive
- * output: TS_ERR_xx. There is no response message.
- *************************************************************************/
-static TSMgmtError
-handle_api_ping(int /* fd */, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallInt stamp;
-
-  return recv_mgmt_request(req, reqlen, OpType::API_PING, &optype, &stamp);
-}
-
-static TSMgmtError
-handle_server_backtrace(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallInt options;
-  MgmtMarshallString trace = nullptr;
-  MgmtMarshallInt err;
-
-  err = recv_mgmt_request(req, reqlen, OpType::SERVER_BACKTRACE, &optype, &options);
-  if (err == TS_ERR_OKAY) {
-    err = ServerBacktrace(options, &trace);
-  }
-
-  err = send_mgmt_response(fd, OpType::SERVER_BACKTRACE, &err, &trace);
-  ats_free(trace);
-
-  return static_cast<TSMgmtError>(err);
-}
-
-static void
-send_record_describe(const RecRecord *rec, void *edata)
-{
-  MgmtMarshallString rec_name      = nullptr;
-  MgmtMarshallData rec_value       = {nullptr, 0};
-  MgmtMarshallData rec_default     = {nullptr, 0};
-  MgmtMarshallInt rec_type         = TS_REC_UNDEFINED;
-  MgmtMarshallInt rec_class        = RECT_NULL;
-  MgmtMarshallInt rec_version      = 0;
-  MgmtMarshallInt rec_rsb          = 0;
-  MgmtMarshallInt rec_order        = 0;
-  MgmtMarshallInt rec_access       = RECA_NULL;
-  MgmtMarshallInt rec_update       = RECU_NULL;
-  MgmtMarshallInt rec_updatetype   = 0;
-  MgmtMarshallInt rec_checktype    = RECC_NULL;
-  MgmtMarshallInt rec_source       = REC_SOURCE_NULL;
-  MgmtMarshallString rec_checkexpr = nullptr;
-
-  TSMgmtError err = TS_ERR_OKAY;
-
-  record_match_state *match = static_cast<record_match_state *>(edata);
-
-  if (match->err != TS_ERR_OKAY) {
-    return;
-  }
-
-  if (rec) {
-    // We only describe config variables (for now).
-    if (!REC_TYPE_IS_CONFIG(rec->rec_type)) {
-      match->err = TS_ERR_PARAMS;
-      return;
-    }
-
-    rec_name       = const_cast<char *>(rec->name);
-    rec_type       = rec->data_type;
-    rec_class      = rec->rec_type;
-    rec_version    = rec->version;
-    rec_rsb        = rec->rsb_id;
-    rec_order      = rec->order;
-    rec_access     = rec->config_meta.access_type;
-    rec_update     = rec->config_meta.update_required;
-    rec_updatetype = rec->config_meta.update_type;
-    rec_checktype  = rec->config_meta.check_type;
-    rec_source     = rec->config_meta.source;
-    rec_checkexpr  = rec->config_meta.check_expr;
-
-    switch (rec_type) {
-    case RECD_INT:
-      rec_type = TS_REC_INT;
-      break;
-    case RECD_FLOAT:
-      rec_type = TS_REC_FLOAT;
-      break;
-    case RECD_STRING:
-      rec_type = TS_REC_STRING;
-      break;
-    case RECD_COUNTER:
-      rec_type = TS_REC_COUNTER;
-      break;
-    default:
-      rec_type = TS_REC_UNDEFINED;
-    }
-
-    err = marshall_rec_data(rec->data_type, rec->data, rec_value);
-    if (err != TS_ERR_OKAY) {
-      goto done;
-    }
-
-    err = marshall_rec_data(rec->data_type, rec->data_default, rec_default);
-    if (err != TS_ERR_OKAY) {
-      goto done;
-    }
-  }
-
-  err = send_mgmt_response(match->fd, OpType::RECORD_DESCRIBE_CONFIG, &err, &rec_name, &rec_value, &rec_default, &rec_type,
-                           &rec_class, &rec_version, &rec_rsb, &rec_order, &rec_access, &rec_update, &rec_updatetype,
-                           &rec_checktype, &rec_source, &rec_checkexpr);
-
-done:
-  match->err = err;
-}
-
-static TSMgmtError
-handle_record_describe(int fd, void *req, size_t reqlen)
-{
-  TSMgmtError ret;
-  record_match_state match;
-  MgmtMarshallInt optype;
-  MgmtMarshallInt options;
-  MgmtMarshallString name;
-
-  ret = recv_mgmt_request(req, reqlen, OpType::RECORD_DESCRIBE_CONFIG, &optype, &name, &options);
-  if (ret != TS_ERR_OKAY) {
-    return ret;
-  }
-
-  if (strlen(name) == 0) {
-    ret = TS_ERR_PARAMS;
-    goto done;
-  }
-
-  match.err = TS_ERR_OKAY;
-  match.fd  = fd;
-
-  if (options & RECORD_DESCRIBE_FLAGS_MATCH) {
-    if (RecLookupMatchingRecords(RECT_CONFIG | RECT_LOCAL, name, send_record_describe, &match) != REC_ERR_OKAY) {
-      ret = TS_ERR_PARAMS;
-      goto done;
-    }
-
-    // If successful, send a list terminator.
-    if (match.err == TS_ERR_OKAY) {
-      send_record_describe(nullptr, &match);
-    }
-
-  } else {
-    if (RecLookupRecord(name, send_record_describe, &match) != REC_ERR_OKAY) {
-      ret = TS_ERR_PARAMS;
-      goto done;
-    }
-  }
-
-  if (ret == TS_ERR_OKAY) {
-    ret = match.err;
-  }
-
-done:
-  ats_free(name);
-  return ret;
-}
-/**************************************************************************
- * handle_lifecycle_message
- *
- * purpose: handle lifecycle message to plugins
- * output: TS_ERR_xx
- * note: None
- *************************************************************************/
-static TSMgmtError
-handle_lifecycle_message(int fd, void *req, size_t reqlen)
-{
-  MgmtMarshallInt optype;
-  MgmtMarshallInt err;
-  MgmtMarshallString tag;
-  MgmtMarshallData data;
-
-  err = recv_mgmt_request(req, reqlen, OpType::LIFECYCLE_MESSAGE, &optype, &tag, &data);
-  if (err == TS_ERR_OKAY) {
-    lmgmt->signalEvent(MGMT_EVENT_LIFECYCLE_MESSAGE, static_cast<char *>(req), reqlen);
-  }
-
-  return send_mgmt_response(fd, OpType::LIFECYCLE_MESSAGE, &err);
-}
-/**************************************************************************/
-
-struct control_message_handler {
-  unsigned flags;
-  TSMgmtError (*handler)(int, void *, size_t);
-};
-
-static const control_message_handler handlers[] = {
-  /* RECORD_SET                 */ {MGMT_API_PRIVILEGED, handle_record_set},
-  /* RECORD_GET                 */ {0, handle_record_get},
-  /* PROXY_STATE_GET            */ {0, handle_proxy_state_get},
-  /* PROXY_STATE_SET            */ {MGMT_API_PRIVILEGED, handle_proxy_state_set},
-  /* RECONFIGURE                */ {MGMT_API_PRIVILEGED, handle_reconfigure},
-  /* RESTART                    */ {MGMT_API_PRIVILEGED, handle_restart},
-  /* BOUNCE                     */ {MGMT_API_PRIVILEGED, handle_restart},
-  /* STOP                       */ {MGMT_API_PRIVILEGED, handle_stop},
-  /* DRAIN                      */ {MGMT_API_PRIVILEGED, handle_drain},
-  /* EVENT_RESOLVE              */ {MGMT_API_PRIVILEGED, handle_event_resolve},
-  /* EVENT_GET_MLT              */ {0, handle_event_get_mlt},
-  /* EVENT_ACTIVE               */ {0, handle_event_active},
-  /* EVENT_REG_CALLBACK         */ {0, nullptr},
-  /* EVENT_UNREG_CALLBACK       */ {0, nullptr},
-  /* EVENT_NOTIFY               */ {0, nullptr},
-  /* STATS_RESET_NODE           */ {MGMT_API_PRIVILEGED, handle_stats_reset},
-  /* STORAGE_DEVICE_CMD_OFFLINE */ {MGMT_API_PRIVILEGED, handle_storage_device_cmd_offline},
-  /* RECORD_MATCH_GET           */ {0, handle_record_match},
-  /* API_PING                   */ {0, handle_api_ping},
-  /* SERVER_BACKTRACE           */ {MGMT_API_PRIVILEGED, handle_server_backtrace},
-  /* RECORD_DESCRIBE_CONFIG     */ {0, handle_record_describe},
-  /* LIFECYCLE_MESSAGE          */ {MGMT_API_PRIVILEGED, handle_lifecycle_message},
-  /* HOST_STATUS_UP             */ {MGMT_API_PRIVILEGED, handle_host_status_up},
-  /* HOST_STATUS_DOWN           */ {MGMT_API_PRIVILEGED, handle_host_status_down},
-};
-
-// This should use countof(), but we need a constexpr :-/
-static_assert((sizeof(handlers) / sizeof(handlers[0])) == static_cast<unsigned>(OpType::UNDEFINED_OP),
-              "handlers array is not of correct size");
-
-static TSMgmtError
-handle_control_message(int fd, void *req, size_t reqlen)
-{
-  OpType optype = extract_mgmt_request_optype(req, reqlen);
-  TSMgmtError error;
-
-  if (static_cast<unsigned>(optype) >= countof(handlers)) {
-    goto fail;
-  }
-
-  if (handlers[static_cast<unsigned>(optype)].handler == nullptr) {
-    goto fail;
-  }
-
-  if (mgmt_has_peereid()) {
-    uid_t euid = -1;
-    gid_t egid = -1;
-
-    // For privileged calls, ensure we have caller credentials and that the caller is privileged.
-    if (handlers[static_cast<unsigned>(optype)].flags & MGMT_API_PRIVILEGED) {
-      if (mgmt_get_peereid(fd, &euid, &egid) == -1 || (euid != 0 && euid != geteuid())) {
-        Debug("ts_main", "denied privileged API access on fd=%d for uid=%d gid=%d", fd, euid, egid);
-        return send_mgmt_error(fd, optype, TS_ERR_PERMISSION_DENIED);
-      }
-    }
-  }
-
-  Debug("ts_main", "handling message type=%d ptr=%p len=%zu on fd=%d", static_cast<int>(optype), req, reqlen, fd);
-
-  error = handlers[static_cast<unsigned>(optype)].handler(fd, req, reqlen);
-  if (error != TS_ERR_OKAY) {
-    // NOTE: if the error was produced by the handler sending a response, this could attempt to
-    // send a response again. However, this would only happen if sending the response failed, so
-    // it is safe to fail to send it again here ...
-    return send_mgmt_error(fd, optype, error);
-  }
-
-  return TS_ERR_OKAY;
-
-fail:
-  mgmt_elog(0, "%s: missing handler for type %d control message\n", __func__, static_cast<int>(optype));
-  return TS_ERR_PARAMS;
-}
diff --git a/mgmt/api/TSControlMain.h b/mgmt/api/TSControlMain.h
deleted file mode 100644
index d7b17b8..0000000
--- a/mgmt/api/TSControlMain.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*****************************************************************************
- * Filename: TSControlMain.h
- * Purpose: The main section for traffic server that handles all the request
- *          from the user.
- * Created: 6/26/00
- * Created by: Stephanie Song
- *
- ***************************************************************************/
-
-#pragma once
-
-#include "mgmtapi.h"
-
-typedef struct {
-  int fd;
-  struct sockaddr *adr;
-} ClientT;
-
-void *ts_ctrl_main(void *arg);
diff --git a/mgmt/api/include/mgmtapi.h b/mgmt/api/include/mgmtapi.h
index 38e85a6..d36181c 100644
--- a/mgmt/api/include/mgmtapi.h
+++ b/mgmt/api/include/mgmtapi.h
@@ -279,25 +279,6 @@
  * API Core
  ***************************************************************************/
 
-/*--- api initialization and shutdown -------------------------------------*/
-/* TSInit: initializations required for API clients
- * Input: socket_path - not applicable for local clients
- *                      for remote users, the path to the config directory.
- *                      If == NULL, we use the Layout engine by default.
- *        options - Control some features of the APIs
- * Output: TS_ERR_xx
- * Note: If remote client successfully connects, returns TS_ERR_OKAY; but
- *       even if not successful connection (eg. client program is started
- *       before TM) then can still make API calls and will try connecting then
- */
-tsapi TSMgmtError TSInit(const char *socket_path, TSInitOptionT options);
-
-/* TSTerminate: does clean up for API clients
- * Input: <none>
- * Output: <none>
- */
-tsapi TSMgmtError TSTerminate();
-
 /*--- plugin initialization -----------------------------------------------*/
 /* TSPluginInit: called by traffic_manager to initialize the plugin
  * Input:  argc - argument count
@@ -313,78 +294,6 @@
 tsapi TSMgmtError TSDisconnectRetrySet(int retries, int retry_sleep_msec);
 tsapi TSMgmtError TSDisconnect();
 
-/*--- control operations --------------------------------------------------*/
-/* TSProxyStateGet: get the proxy state (on/off)
- * Input:  <none>
- * Output: proxy state (on/off)
- */
-tsapi TSProxyStateT TSProxyStateGet();
-
-/* TSProxyStateSet: set the proxy state (on/off)
- * Input:  proxy_state - set to on/off
- *         clear - a TSCacheClearT bitmask,
- *            specifies if want to start TS with clear_cache or
- *            clear_cache_hostdb option, or just run TS with no options;
- *            only applies when turning proxy on
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSProxyStateSet(TSProxyStateT proxy_state, unsigned clear);
-
-/* TSProxyBacktraceGet: get a backtrace of the proxy
- * Input:  unsigned options - stack trace options
- * Output: formatted backtrace of the proxy
- * 	the caller must free this with TSfree
- */
-tsapi TSMgmtError TSProxyBacktraceGet(unsigned, TSString *);
-
-/* TSReconfigure: tell traffic_server to re-read its configuration files
- * Input:  <none>
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSReconfigure();
-
-/* TSRestart: restarts Traffic Manager and Traffic Server
- * Input: options - bitmask of TSRestartOptionT
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSRestart(unsigned options);
-
-/* TSActionDo: based on TSActionNeedT, will take appropriate action
- * Input: action - action that needs to be taken
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSActionDo(TSActionNeedT action);
-
-/* TSBounce: restart the traffic_server process(es).
- * Input: options - bitmask of TSRestartOptionT
- * Output TSMgmtError
- */
-tsapi TSMgmtError TSBounce(unsigned options);
-
-/* TSStop: stop the traffic_server process(es).
- * Input: options - bitmask of TSRestartOptionT
- * Output TSMgmtError
- */
-tsapi TSMgmtError TSStop(unsigned options);
-
-/* TSDrain: drain requests of the traffic_server process.
- * Input: options - TSDrainOptionT
- * Output TSMgmtError
- */
-tsapi TSMgmtError TSDrain(unsigned options);
-
-/* TSStorageDeviceCmdOffline: Request to make a cache storage device offline.
- * @arg dev Target device, specified by path to device.
- * @return Success.
- */
-tsapi TSMgmtError TSStorageDeviceCmdOffline(const char *dev);
-
-/* TSLifecycleMessage: Send a lifecycle message to the plugins.
- * @arg tag Alert tag string (null-terminated)
- * @return Success
- */
-tsapi TSMgmtError TSLifecycleMessage(const char *tag, void const *data, size_t data_size);
-
 /* TSGetErrorMessage: convert error id to error message
  * Input:  error id (defined in TSMgmtError)
  * Output: corresponding error message (allocated memory)
@@ -428,164 +337,6 @@
  * NOTE: header and headerSize can be NULL
  */
 tsapi TSMgmtError TSReadFromUrlEx(const char *url, char **header, int *headerSize, char **body, int *bodySize, int timeout);
-tsapi TSMgmtError TSHostStatusSetUp(const char *host_name, int down_time, const char *reason);
-tsapi TSMgmtError TSHostStatusSetDown(const char *host_name, int down_time, const char *reason);
-/*--- statistics operations -----------------------------------------------*/
-/* TSStatsReset: sets all the statistics variables to their default values
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSStatsReset(const char *name);
-
-/*--- variable operations -------------------------------------------------*/
-/* TSRecordGet: gets a record
- * Input:  rec_name - the name of the record (proxy.config.record_name)
- *         rec_val  - allocated TSRecordEle structure, value stored inside
- * Output: TSMgmtError (if the rec_name does not exist, returns TS_ERR_FAIL)
- */
-tsapi TSMgmtError TSRecordGet(const char *rec_name, TSRecordEle *rec_val);
-
-/* TSRecordGet*: gets a record w/ a known type
- * Input:  rec_name - the name of the record (proxy.config.record_name)
- *         *_val    - allocated TSRecordEle structure, value stored inside
- * Output: TSMgmtError
- * Note: For TSRecordGetString, the function will allocate memory for the
- *       *string_val, so the caller must free (*string_val);
- */
-tsapi TSMgmtError TSRecordGetInt(const char *rec_name, TSInt *int_val);
-tsapi TSMgmtError TSRecordGetCounter(const char *rec_name, TSCounter *counter_val);
-tsapi TSMgmtError TSRecordGetFloat(const char *rec_name, TSFloat *float_val);
-tsapi TSMgmtError TSRecordGetString(const char *rec_name, TSString *string_val);
-
-/* TSRecordGetMlt: gets a set of records
- * Input:  rec_list - list of record names the user wants to retrieve;
- *                    resulting gets will be stored in the same list;
- *                    if one get fails, transaction will be aborted
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSRecordGetMlt(TSStringList rec_names, TSList rec_vals);
-
-/* TSRecordGetMatchMlt: gets a set of records
- * Input:  rec_regex - regular expression to match against record names
- * Output: TSMgmtError, TSList of TSRecordEle
- */
-tsapi TSMgmtError TSRecordGetMatchMlt(const char *rec_regex, TSList list);
-
-/* TSRecordSet*: sets a record w/ a known type
- * Input:  rec_name     - the name of the record (proxy.config.record_name)
- *         *_val        - the value to set the record to
- *         *action_need - indicates which operation required by user for changes to take effect
- * Output: TSMgmtError
- */
-
-tsapi TSMgmtError TSRecordSet(const char *rec_name, const char *val, TSActionNeedT *action_need);
-tsapi TSMgmtError TSRecordSetInt(const char *rec_name, TSInt int_val, TSActionNeedT *action_need);
-tsapi TSMgmtError TSRecordSetCounter(const char *rec_name, TSCounter counter_val, TSActionNeedT *action_need);
-tsapi TSMgmtError TSRecordSetFloat(const char *rec_name, TSFloat float_val, TSActionNeedT *action_need);
-tsapi TSMgmtError TSRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT *action_need);
-
-/* TSConfigRecordDescribe: fetch a full description of a configuration record
- * Input: rec_name  - name of the record
- *        flags     - (unused) fetch flags bitmask
- *        val       - output value;
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSConfigRecordDescribe(const char *rec_name, unsigned flags, TSConfigRecordDescription *val);
-tsapi TSMgmtError TSConfigRecordDescribeMatchMlt(const char *rec_regex, unsigned flags, TSList list);
-
-/* TSRecordSetMlt: sets a set of records
- * Input:  rec_list     - list of record names the user wants to set;
- *                        if one set fails, transaction will be aborted
- *         *action_need - indicates which operation required by user for changes to take effect
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSRecordSetMlt(TSList rec_list, TSActionNeedT *action_need);
-
-/*--- events --------------------------------------------------------------*/
-/* Only a set of statically defined events exist. An event is either
- * active or inactive. An event is active when it is triggered, and
- * becomes inactive when resolved. Events are triggered and resolved
- * by specifying the event's name (which is predefined and immutable).
- */
-
-/* TSEventResolve: enables the user to resolve an event
- * Input:  event_name - event to resolve
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSEventResolve(const char *event_name);
-
-/* TSActiveEventGetMlt: query for a list of all the currently active events
- * Input:  active_events - an empty TSList; if function call is successful,
- *                         active_events will contain names of the currently
- *                         active events
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSActiveEventGetMlt(TSList active_events);
-
-/* TSEventIsActive: check if the specified event is active
- * Input:  event_name - name of event to check if active; must be one of
- *                      the predefined names
- *         is_current - when function completes, if true, then the event is
- *                      active
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSEventIsActive(char *event_name, bool *is_current);
-
-/* TSEventSignalCbRegister: register a callback for a specific event or
- *                           for any event
- * Input:  event_name - the name of event to register callback for;
- *                      if NULL, the callback is registered for all events
- *         func       - callback function
- *         data       - data to pass to callback
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSEventSignalCbRegister(char *event_name, TSEventSignalFunc func, void *data);
-
-/* TSEventSignalCbUnregister: unregister a callback for a specific event
- *                             or for any event
- * Input: event_name - the name of event to unregister callback for;
- *                     if NULL, the callback is unregistered for all events
- *         func       - callback function
- * Output: TSMgmtError
- */
-tsapi TSMgmtError TSEventSignalCbUnregister(char *event_name, TSEventSignalFunc func);
-
-/*--- TS Cache Inspector Operations --------------------------------------------*/
-
-/* TSLookupFromCacheUrl
- *   Function takes an url and an 'info' buffer as input,
- *   lookups cache information of the url and saves the
- *   cache info to the info buffer
- */
-tsapi TSMgmtError TSLookupFromCacheUrl(TSString url, TSString *info);
-
-/* TSLookupFromCacheUrlRegex
- *   Function takes a string in a regex form and returns
- *   a list of urls that match the regex
- ********************************************************/
-
-tsapi TSMgmtError TSLookupFromCacheUrlRegex(TSString url_regex, TSString *list);
-
-/* TSDeleteFromCacheUrl
- *   Function takes an url and an 'info' buffer as input,
- *   deletes the url from cache if it's in the cache and
- *   returns the status of deletion
- ********************************************************/
-
-tsapi TSMgmtError TSDeleteFromCacheUrl(TSString url, TSString *info);
-
-/* TSDeleteFromCacheUrlRegex
- *   Function takes a string in a regex form and returns
- *   a list of urls deleted from cache
- ********************************************************/
-
-tsapi TSMgmtError TSDeleteFromCacheUrlRegex(TSString url_regex, TSString *list);
-
-/* TSInvalidateFromCacheUrlRegex
- *   Function takes a string in a regex form and returns
- *   a list of urls invalidated from cache
- ********************************************************/
-
-tsapi TSMgmtError TSInvalidateFromCacheUrlRegex(TSString url_regex, TSString *list);
 
 #ifdef __cplusplus
 }
diff --git a/src/traffic_manager/AddConfigFilesHere.cc b/mgmt/config/AddConfigFilesHere.cc
similarity index 81%
rename from src/traffic_manager/AddConfigFilesHere.cc
rename to mgmt/config/AddConfigFilesHere.cc
index ec2a0f2..40cc844 100644
--- a/src/traffic_manager/AddConfigFilesHere.cc
+++ b/mgmt/config/AddConfigFilesHere.cc
@@ -23,11 +23,10 @@
 
 #include "tscore/ink_platform.h"
 #include "tscore/Filenames.h"
-#include "MgmtUtils.h"
+#include "records/P_RecCore.h"
 #include "tscore/Diags.h"
 #include "FileManager.h"
-
-extern FileManager *configFiles;
+#include "tscore/Errata.h"
 
 static constexpr bool REQUIRED{true};
 static constexpr bool NOT_REQUIRED{false};
@@ -37,23 +36,12 @@
  *
  *
  ****************************************************************************/
-
 void
-testcall(char *foo, char * /*configName */)
+registerFile(const char *configName, const char *defaultName, bool isRequired)
 {
-  Debug("lm", "Received Callback that %s has changed", foo);
-}
-
-void
-registerFile(const char *configName, const char *defaultName, bool isRequired, bool isElevateNeeded = false)
-{
-  bool found  = false;
-  char *fname = REC_readString(configName, &found);
-  if (!found) {
-    fname = ats_strdup(defaultName);
-  }
-  configFiles->addFile(fname, configName, isElevateNeeded, isRequired);
-  ats_free(fname);
+  bool found = false;
+  ats_scoped_str fname(REC_readString(configName, &found));
+  FileManager::instance().addFile(found ? fname : defaultName, configName, false, isRequired);
 }
 
 //
@@ -88,10 +76,7 @@
   registerFile("proxy.config.cache.hosting_filename", ts::filename::HOSTING, NOT_REQUIRED);
   registerFile("", ts::filename::PLUGIN, NOT_REQUIRED);
   registerFile("proxy.config.dns.splitdns.filename", ts::filename::SPLITDNS, NOT_REQUIRED);
-  uint32_t elevate_setting = 0;
-  REC_ReadConfigInteger(elevate_setting, "proxy.config.ssl.cert.load_elevated");
-  registerFile("proxy.config.ssl.server.multicert.filename", ts::filename::SSL_MULTICERT, NOT_REQUIRED, elevate_setting);
+  registerFile("proxy.config.ssl.server.multicert.filename", ts::filename::SSL_MULTICERT, NOT_REQUIRED);
   registerFile("proxy.config.ssl.servername.filename", ts::filename::SNI, NOT_REQUIRED);
-
-  configFiles->registerCallback(testcall);
+  registerFile("proxy.config.jsonrpc.filename", ts::filename::JSONRPC, NOT_REQUIRED);
 }
diff --git a/mgmt/config/FileManager.cc b/mgmt/config/FileManager.cc
new file mode 100644
index 0000000..fd084cd
--- /dev/null
+++ b/mgmt/config/FileManager.cc
@@ -0,0 +1,442 @@
+/** @file
+
+  Code for class to manage configuration updates
+
+  @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 "FileManager.h"
+
+#include <vector>
+#include <algorithm>
+
+#include "InkAPIInternal.h" // TODO: this brings a lot of dependencies, double check this.
+
+#include "tscore/ink_platform.h"
+#include "tscore/ink_file.h"
+#include "records/P_RecCore.h"
+#include "tscore/Diags.h"
+#include "tscore/Filenames.h"
+#include "tscore/I_Layout.h"
+#include <tscore/BufferWriter.h>
+
+#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
+#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtimespec.tv_nsec)
+#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtim.tv_nsec)
+#else
+#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000)
+#endif
+
+static constexpr auto logTag{"filemanager"};
+namespace
+{
+ts::Errata
+handle_file_reload(std::string const &fileName, std::string const &configName)
+{
+  Debug(logTag, "handling reload %s - %s", fileName.c_str(), configName.c_str());
+  ts::Errata ret;
+  // TODO: make sure records holds the name after change, if not we should change it.
+  if (fileName == ts::filename::RECORDS) {
+    if (RecReadConfigFile() == REC_ERR_OKAY) {
+      RecConfigWarnIfUnregistered();
+    } else {
+      std::string str;
+      ret.push(1, ts::bwprint(str, "Error reading {}.", fileName));
+    }
+  } else {
+    RecT rec_type;
+    char *data = const_cast<char *>(configName.c_str());
+    if (RecGetRecordType(data, &rec_type) == REC_ERR_OKAY && rec_type == RECT_CONFIG) {
+      RecSetSyncRequired(data);
+    } else {
+      std::string str;
+      ret.push(1, ts::bwprint(str, "Unknown file change {}.", configName));
+    }
+  }
+
+  return ret;
+}
+
+// JSONRPC endpoint defs.
+const std::string CONFIG_REGISTRY_KEY_STR{"config_registry"};
+const std::string FILE_PATH_KEY_STR{"file_path"};
+const std::string RECORD_NAME_KEY_STR{"config_record_name"};
+const std::string PARENT_CONFIG_KEY_STR{"parent_config"};
+const std::string ROOT_ACCESS_NEEDED_KEY_STR{"root_access_needed"};
+const std::string IS_REQUIRED_KEY_STR{"is_required"};
+const std::string NA_STR{"N/A"};
+
+} // namespace
+
+FileManager::FileManager()
+{
+  ink_mutex_init(&accessLock);
+  this->registerCallback(&handle_file_reload);
+
+  // Register the files registry jsonrpc endpoint
+  rpc::add_method_handler("filemanager.get_files_registry",
+                          [this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> {
+                            return get_files_registry_rpc_endpoint(id, req);
+                          },
+                          &rpc::core_ats_rpc_service_provider_handle, {{rpc::NON_RESTRICTED_API}});
+}
+
+// FileManager::~FileManager
+//
+//  There is only FileManager object in the process and it
+//     should never need to be destructed except at
+//     program exit
+//
+FileManager::~FileManager()
+{
+  // Let other operations finish and do not start any new ones
+  ink_mutex_acquire(&accessLock);
+
+  for (auto &&it : bindings) {
+    delete it.second;
+  }
+
+  ink_mutex_release(&accessLock);
+  ink_mutex_destroy(&accessLock);
+}
+
+// void FileManager::addFile(char* fileName, const configFileInfo* file_info,
+//  ConfigManager* parentConfig)
+//
+//  for the baseFile, creates a ConfigManager object for it
+//
+//  if file_info is not null, a WebFileEdit object is also created for
+//    the file
+//
+//  Pointers to the new objects are stored in the bindings hashtable
+//
+void
+FileManager::addFile(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
+                     ConfigManager *parentConfig)
+{
+  ink_mutex_acquire(&accessLock);
+  addFileHelper(fileName, configName, root_access_needed, isRequired, parentConfig);
+  ink_mutex_release(&accessLock);
+}
+
+// caller must hold the lock
+void
+FileManager::addFileHelper(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
+                           ConfigManager *parentConfig)
+{
+  ink_assert(fileName != nullptr);
+  ConfigManager *configManager = new ConfigManager(fileName, configName, root_access_needed, isRequired, parentConfig);
+  bindings.emplace(configManager->getFileName(), configManager);
+}
+
+// bool FileManager::getConfigManagerObj(char* fileName, ConfigManager** rbPtr)
+//
+//  Sets rbPtr to the ConfigManager object associated
+//    with the passed in fileName.
+//
+//  If there is no binding, false is returned
+//
+bool
+FileManager::getConfigObj(const char *fileName, ConfigManager **rbPtr)
+{
+  ink_mutex_acquire(&accessLock);
+  auto it    = bindings.find(fileName);
+  bool found = it != bindings.end();
+  ink_mutex_release(&accessLock);
+
+  *rbPtr = found ? it->second : nullptr;
+  return found;
+}
+
+ts::Errata
+FileManager::fileChanged(std::string const &fileName, std::string const &configName)
+{
+  Debug("filemanager", "file changed %s", fileName.c_str());
+  ts::Errata ret;
+
+  std::lock_guard<std::mutex> guard(_callbacksMutex);
+  for (auto const &call : _configCallbacks) {
+    if (auto const &r = call(fileName, configName); !r) {
+      Debug("filemanager", "something back from callback %s", fileName.c_str());
+      std::for_each(r.begin(), r.end(), [&ret](auto &&e) { ret.push(e); });
+    }
+  }
+
+  return ret;
+}
+
+// TODO: To do the following here, we have to pull up a lot of dependencies we don't really
+// need, #include "InkAPIInternal.h" brings plenty of them. Double check this approach. RPC will
+// also be able to pass messages to plugins, once that's designed it can also cover this.
+void
+FileManager::registerConfigPluginCallbacks(ConfigUpdateCbTable *cblist)
+{
+  _pluginCallbackList = cblist;
+}
+
+void
+FileManager::invokeConfigPluginCallbacks()
+{
+  Debug("filemanager", "invoke plugin callbacks");
+  static const std::string_view s{"*"};
+  if (_pluginCallbackList) {
+    _pluginCallbackList->invoke(s.data());
+  }
+}
+
+// void FileManger::rereadConfig()
+//
+//   Iterates through the list of managed files and
+//     calls ConfigManager::checkForUserUpdate on them
+//
+//   although it is tempting, DO NOT CALL FROM SIGNAL HANDLERS
+//      This function is not Async-Signal Safe.  It
+//      is thread safe
+ts::Errata
+FileManager::rereadConfig()
+{
+  ts::Errata ret;
+
+  ConfigManager *rb;
+  std::vector<ConfigManager *> changedFiles;
+  std::vector<ConfigManager *> parentFileNeedChange;
+  size_t n;
+  ink_mutex_acquire(&accessLock);
+  for (auto &&it : bindings) {
+    rb = it.second;
+    // ToDo: rb->isVersions() was always true before, because numberBackups was always >= 1. So ROLLBACK_CHECK_ONLY could not
+    // happen at all...
+    if (rb->checkForUserUpdate(FileManager::ROLLBACK_CHECK_AND_UPDATE)) {
+      Debug(logTag, "File %s changed.", it.first.c_str());
+      auto const &r = fileChanged(rb->getFileName(), rb->getConfigName());
+
+      if (!r) {
+        std::for_each(r.begin(), r.end(), [&ret](auto &&e) { ret.push(e); });
+      }
+
+      changedFiles.push_back(rb);
+      if (rb->isChildManaged()) {
+        if (std::find(parentFileNeedChange.begin(), parentFileNeedChange.end(), rb->getParentConfig()) ==
+            parentFileNeedChange.end()) {
+          parentFileNeedChange.push_back(rb->getParentConfig());
+        }
+      }
+    }
+  }
+
+  std::vector<ConfigManager *> childFileNeedDelete;
+  n = changedFiles.size();
+  for (size_t i = 0; i < n; i++) {
+    if (changedFiles[i]->isChildManaged()) {
+      continue;
+    }
+    // for each parent file, if it is changed, then delete all its children
+    for (auto &&it : bindings) {
+      rb = it.second;
+      if (rb->getParentConfig() == changedFiles[i]) {
+        if (std::find(childFileNeedDelete.begin(), childFileNeedDelete.end(), rb) == childFileNeedDelete.end()) {
+          childFileNeedDelete.push_back(rb);
+        }
+      }
+    }
+  }
+  n = childFileNeedDelete.size();
+  for (size_t i = 0; i < n; i++) {
+    bindings.erase(childFileNeedDelete[i]->getFileName());
+    delete childFileNeedDelete[i];
+  }
+  ink_mutex_release(&accessLock);
+
+  n = parentFileNeedChange.size();
+  for (size_t i = 0; i < n; i++) {
+    if (std::find(changedFiles.begin(), changedFiles.end(), parentFileNeedChange[i]) == changedFiles.end()) {
+      if (auto const &r = fileChanged(parentFileNeedChange[i]->getFileName(), parentFileNeedChange[i]->getConfigName()); !r) {
+        std::for_each(r.begin(), r.end(), [&ret](auto &&e) { ret.push(e); });
+      }
+    }
+  }
+  // INKqa11910
+  // need to first check that enable_customizations is enabled
+  bool found;
+  int enabled = static_cast<int>(REC_readInteger("proxy.config.body_factory.enable_customizations", &found));
+
+  if (found && enabled) {
+    if (auto const &r = fileChanged("proxy.config.body_factory.template_sets_dir", "proxy.config.body_factory.template_sets_dir");
+        !r) {
+      std::for_each(r.begin(), r.end(), [&ret](auto &&e) { ret.push(e); });
+    }
+  }
+
+  if (auto const &r = fileChanged("proxy.config.ssl.server.ticket_key.filename", "proxy.config.ssl.server.ticket_key.filename");
+      !r) {
+    std::for_each(r.begin(), r.end(), [&ret](auto &&e) { ret.push(e); });
+  }
+
+  return ret;
+}
+
+bool
+FileManager::isConfigStale()
+{
+  ConfigManager *rb;
+  bool stale = false;
+
+  ink_mutex_acquire(&accessLock);
+  for (auto &&it : bindings) {
+    rb = it.second;
+    if (rb->checkForUserUpdate(FileManager::ROLLBACK_CHECK_ONLY)) {
+      stale = true;
+      break;
+    }
+  }
+
+  ink_mutex_release(&accessLock);
+  return stale;
+}
+
+// void configFileChild(const char *parent, const char *child)
+//
+// Add child to the bindings with parentConfig
+void
+FileManager::configFileChild(const char *parent, const char *child)
+{
+  ConfigManager *parentConfig = nullptr;
+  ink_mutex_acquire(&accessLock);
+  if (auto it = bindings.find(parent); it != bindings.end()) {
+    Debug(logTag, "Adding child file %s to %s parent", child, parent);
+    parentConfig = it->second;
+    addFileHelper(child, "", parentConfig->rootAccessNeeded(), parentConfig->getIsRequired(), parentConfig);
+  }
+  ink_mutex_release(&accessLock);
+}
+
+auto
+FileManager::get_files_registry_rpc_endpoint(std::string_view const &id, YAML::Node const &params) -> ts::Rv<YAML::Node>
+{
+  // If any error, the rpc manager will catch it and respond with it.
+  YAML::Node configs{YAML::NodeType::Sequence};
+  {
+    ink_scoped_mutex_lock lock(accessLock);
+    for (auto &&it : bindings) {
+      if (ConfigManager *cm = it.second; cm) {
+        YAML::Node element{YAML::NodeType::Map};
+        std::string sysconfdir(RecConfigReadConfigDir());
+        element[FILE_PATH_KEY_STR]          = Layout::get()->relative_to(sysconfdir, cm->getFileName());
+        element[RECORD_NAME_KEY_STR]        = cm->getConfigName();
+        element[PARENT_CONFIG_KEY_STR]      = (cm->isChildManaged() ? cm->getParentConfig()->getFileName() : NA_STR);
+        element[ROOT_ACCESS_NEEDED_KEY_STR] = cm->rootAccessNeeded();
+        element[IS_REQUIRED_KEY_STR]        = cm->getIsRequired();
+        configs.push_back(element);
+      }
+    }
+  }
+
+  YAML::Node registry;
+  registry[CONFIG_REGISTRY_KEY_STR] = configs;
+  return registry;
+}
+
+/// ConfigFile
+
+FileManager::ConfigManager::ConfigManager(const char *fileName_, const char *configName_, bool root_access_needed_,
+                                          bool isRequired_, ConfigManager *parentConfig_)
+  : root_access_needed(root_access_needed_), isRequired(isRequired_), parentConfig(parentConfig_)
+{
+  // ExpandingArray existVer(25, true); // Existing versions
+  struct stat fileInfo;
+  ink_assert(fileName_ != nullptr);
+
+  // parent must not also have a parent
+  if (parentConfig) {
+    ink_assert(parentConfig->parentConfig == nullptr);
+  }
+
+  // Copy the file name.
+  fileName   = ats_strdup(fileName_);
+  configName = ats_strdup(configName_);
+
+  ink_mutex_init(&fileAccessLock);
+  // Check to make sure that our configuration file exists
+  //
+  if (statFile(&fileInfo) < 0) {
+    Debug(logTag, "%s  Unable to load: %s", fileName, strerror(errno));
+
+    if (isRequired) {
+      Debug(logTag, " Unable to open required configuration file %s\n\t failed :%s", fileName, strerror(errno));
+    }
+  } else {
+    fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
+  }
+}
+
+FileManager::ConfigManager::~ConfigManager()
+{
+  ats_free(fileName);
+  ats_free(configName);
+}
+
+//
+//
+// int ConfigManager::statFile()
+//
+//  A wrapper for stat()
+//
+int
+FileManager::ConfigManager::statFile(struct stat *buf)
+{
+  int statResult;
+  std::string sysconfdir(RecConfigReadConfigDir());
+  std::string filePath = Layout::get()->relative_to(sysconfdir, fileName);
+
+  statResult = root_access_needed ? elevating_stat(filePath.c_str(), buf) : stat(filePath.c_str(), buf);
+
+  return statResult;
+}
+
+// bool ConfigManager::checkForUserUpdate(RollBackCheckType how)
+//
+//  Called to check if the file has been changed  by the user.
+//  Timestamps are compared to see if a change occurred
+bool
+FileManager::ConfigManager::checkForUserUpdate(FileManager::RollBackCheckType how)
+{
+  struct stat fileInfo;
+  bool result;
+
+  ink_mutex_acquire(&fileAccessLock);
+
+  if (this->statFile(&fileInfo) < 0) {
+    ink_mutex_release(&fileAccessLock);
+    return false;
+  }
+
+  if (fileLastModified < TS_ARCHIVE_STAT_MTIME(fileInfo)) {
+    if (how == FileManager::ROLLBACK_CHECK_AND_UPDATE) {
+      fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
+      // TODO: syslog????
+    }
+    Debug(logTag, "User has changed config file %s\n", fileName);
+    result = true;
+  } else {
+    result = false;
+  }
+
+  ink_mutex_release(&fileAccessLock);
+  return result;
+}
diff --git a/mgmt/config/FileManager.h b/mgmt/config/FileManager.h
new file mode 100644
index 0000000..4746e67
--- /dev/null
+++ b/mgmt/config/FileManager.h
@@ -0,0 +1,176 @@
+/** @file
+
+  Interface for class to manage configuration updates
+
+  @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.
+ */
+
+#pragma once
+
+#include "tscore/ink_mutex.h"
+#include "tscore/List.h"
+
+#include "tscore/Errata.h"
+
+#include <rpc/jsonrpc/JsonRPC.h>
+
+#include <unordered_map>
+#include <string_view>
+#include <forward_list>
+#include <mutex>
+#include <functional>
+
+class ConfigUpdateCbTable;
+
+class FileManager
+{
+public:
+  enum RollBackCheckType {
+    ROLLBACK_CHECK_AND_UPDATE,
+    ROLLBACK_CHECK_ONLY,
+  };
+  class ConfigManager
+  {
+  public:
+    // fileName_ should be rooted or a base file name.
+    ConfigManager(const char *fileName_, const char *configName_, bool root_access_needed, bool isRequired_,
+                  ConfigManager *parentConfig_);
+    ~ConfigManager();
+
+    // Manual take out of lock required
+    void
+    acquireLock()
+    {
+      ink_mutex_acquire(&fileAccessLock);
+    };
+
+    void
+    releaseLock()
+    {
+      ink_mutex_release(&fileAccessLock);
+    };
+
+    // Check if a file has changed, automatically holds the lock. Used by FileManager.
+    bool checkForUserUpdate(FileManager::RollBackCheckType);
+
+    // These are getters, for FileManager to get info about a particular configuration.
+    const char *
+    getFileName() const
+    {
+      return fileName;
+    }
+
+    const char *
+    getConfigName() const
+    {
+      return configName;
+    }
+
+    bool
+    isChildManaged() const
+    {
+      return parentConfig != nullptr;
+    }
+
+    ConfigManager *
+    getParentConfig() const
+    {
+      return parentConfig;
+    }
+
+    bool
+    rootAccessNeeded() const
+    {
+      return root_access_needed;
+    }
+
+    bool
+    getIsRequired() const
+    {
+      return isRequired;
+    }
+
+    // noncopyable
+    ConfigManager(const ConfigManager &) = delete;
+    ConfigManager &operator=(const ConfigManager &) = delete;
+
+  private:
+    int statFile(struct stat *buf);
+
+    ink_mutex fileAccessLock;
+    char *fileName;
+    char *configName;
+    bool root_access_needed;
+    bool isRequired;
+    ConfigManager *parentConfig;
+    time_t fileLastModified = 0;
+  };
+
+  using CallbackType = std::function<ts::Errata(std::string const &, std::string const &)>;
+
+  ~FileManager();
+  FileManager(const FileManager &obj) = delete;
+  FileManager &operator=(FileManager const &) = delete;
+
+  void addFile(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
+               ConfigManager *parentConfig = nullptr);
+
+  bool getConfigObj(const char *fileName, ConfigManager **rbPtr);
+
+  void
+  registerCallback(CallbackType f)
+  {
+    std::lock_guard<std::mutex> guard(_callbacksMutex);
+    _configCallbacks.push_front(std::move(f));
+  }
+
+  ts::Errata fileChanged(std::string const &fileName, std::string const &configName);
+  ts::Errata rereadConfig();
+  bool isConfigStale();
+  void configFileChild(const char *parent, const char *child);
+
+  void registerConfigPluginCallbacks(ConfigUpdateCbTable *cblist);
+  void invokeConfigPluginCallbacks();
+
+  static FileManager &
+  instance()
+  {
+    static FileManager configFiles;
+    return configFiles;
+  }
+
+private:
+  FileManager();
+
+  ink_mutex accessLock; // Protects bindings hashtable
+  ConfigUpdateCbTable *_pluginCallbackList;
+
+  std::mutex _callbacksMutex;
+  std::mutex _accessMutex;
+
+  std::forward_list<CallbackType> _configCallbacks;
+
+  std::unordered_map<std::string, ConfigManager *> bindings;
+  void addFileHelper(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
+                     ConfigManager *parentConfig);
+  /// JSONRPC endpoint
+  ts::Rv<YAML::Node> get_files_registry_rpc_endpoint(std::string_view const &id, YAML::Node const &params);
+};
+
+void initializeRegistry(); // implemented in AddConfigFilesHere.cc
diff --git a/mgmt/config/Makefile.am b/mgmt/config/Makefile.am
new file mode 100644
index 0000000..81d8ec6
--- /dev/null
+++ b/mgmt/config/Makefile.am
@@ -0,0 +1,62 @@
+#
+# Makefile.am for the Enterprise Management module.
+#
+#  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.
+
+
+
+AM_CPPFLAGS += \
+  $(iocore_include_dirs) \
+  -I$(abs_top_srcdir)/iocore/utils \
+  -I$(abs_top_srcdir)/include \
+  -I$(abs_top_srcdir)/lib/ \
+  -I$(abs_top_srcdir)/mgmt/rpc \
+  -I$(abs_top_srcdir)/mgmt/ \
+  -I$(abs_top_srcdir)/mgmt/utils \
+  -I$(abs_top_srcdir)/proxy/ \
+  -I$(abs_top_srcdir)/proxy/http \
+  -I$(abs_top_srcdir)/proxy/hdrs \
+  $(TS_INCLUDES) @SWOC_INCLUDES@ \
+  @YAMLCPP_INCLUDES@
+
+# ^^ all the proxy/* is to include the PluginCallbacks.
+
+noinst_LTLIBRARIES = libconfigmanager.la
+#check_PROGRAMS = test_configfiles
+
+
+TESTS = $(check_PROGRAMS)
+
+# Protocol library only, no transport.
+libconfigmanager_COMMON = \
+	FileManager.h \
+	FileManager.cc \
+	AddConfigFilesHere.cc
+
+
+libconfigmanager_la_SOURCES = \
+	$(libconfigmanager_COMMON)
+
+libconfigmanager_la_LIBADD = \
+	$(top_builddir)/src/tscore/libtscore.la
+
+
+include $(top_srcdir)/build/tidy.mk
+
+clang-tidy-local: $(DIST_SOURCES)
+	$(CXX_Clang_Tidy)
+
diff --git a/mgmt/rpc/Makefile.am b/mgmt/rpc/Makefile.am
new file mode 100644
index 0000000..d4b12b4
--- /dev/null
+++ b/mgmt/rpc/Makefile.am
@@ -0,0 +1,192 @@
+#
+# Makefile.am for the RPC/jsonrpc module
+#
+#  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.
+
+AM_CPPFLAGS += \
+	$(iocore_include_dirs) \
+	-I$(abs_top_srcdir)/iocore/utils \
+	-I$(abs_top_srcdir)/include \
+	-I$(abs_top_srcdir)/lib/ \
+	-I$(abs_top_srcdir)/mgmt/ \
+	$(TS_INCLUDES) @SWOC_INCLUDES@ \
+	@YAMLCPP_INCLUDES@
+
+
+noinst_LTLIBRARIES = libjsonrpc_protocol.la libjsonrpc_server.la librpcpublichandlers.la
+check_PROGRAMS = test_jsonrpc test_jsonrpcserver
+
+
+TESTS = $(check_PROGRAMS)
+
+# TODO: Remove libmgmt_p.la This can only be removed once we remove the ProcessManager dependency and the librecords
+#       Message stuff between TM and TS
+# TODO: handlers - mgmt/utils needed as ProcessManager.h is included in many pleaces, we should be able to remove it once
+#       we move away from TM
+
+###########################################################################################
+# Protocol library only, no transport.
+
+libjsonrpc_protocol_COMMON = \
+	jsonrpc/error/RPCError.cc \
+	jsonrpc/error/RPCError.h \
+	jsonrpc/JsonRPCManager.cc \
+	jsonrpc/JsonRPCManager.h \
+	jsonrpc/Context.cc \
+	jsonrpc/Context.h
+
+libjsonrpc_protocol_la_SOURCES = \
+	$(libjsonrpc_protocol_COMMON)
+
+
+test_jsonrpc_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(abs_top_srcdir)/tests/include \
+	@YAMLCPP_INCLUDES@
+
+test_jsonrpc_LDFLAGS = \
+	@AM_LDFLAGS@
+
+test_jsonrpc_SOURCES = \
+	jsonrpc/unit_tests/unit_test_main.cc \
+	jsonrpc/unit_tests/test_basic_protocol.cc
+
+# TODO: Remove libmgmt_p.la
+
+test_jsonrpc_LDADD = \
+	libjsonrpc_protocol.la \
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	$(top_builddir)/src/records/librecords_p.a \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/src/records/librecords_p.a \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/mgmt/libmgmt_p.la \
+	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
+	@YAMLCPP_LIBS@ @HWLOC_LIBS@
+
+
+
+###########################################################################################
+# RPC server only.
+libjsonrpc_server_COMMON = \
+	server/RPCServer.cc \
+	server/RPCServer.h \
+	server/CommBase.cc \
+	server/CommBase.h \
+	server/IPCSocketServer.cc \
+	server/IPCSocketServer.h \
+	config/JsonRPCConfig.cc \
+	config/JsonRPCConfig.h
+
+libjsonrpc_server_la_SOURCES = \
+	$(libjsonrpc_server_COMMON)
+
+test_jsonrpcserver_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(abs_top_srcdir)/tests/include \
+	-I$(abs_top_srcdir)/tests \
+	@YAMLCPP_INCLUDES@
+
+test_jsonrpcserver_LDFLAGS = \
+	@AM_LDFLAGS@
+
+test_jsonrpcserver_SOURCES = \
+	server/unit_tests/unit_test_main.cc \
+	$(shared_rpc_ipc_client_SOURCES) \
+	server/unit_tests/test_rpcserver.cc
+
+test_jsonrpcserver_LDADD = \
+	libjsonrpc_protocol.la \
+	libjsonrpc_server.la \
+	$(top_builddir)/src/records/librecords_p.a \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/src/records/librecords_p.a \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/mgmt/libmgmt_p.la \
+	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
+	@YAMLCPP_LIBS@ @HWLOC_LIBS@
+
+
+###########################################################################################
+# Handlers only
+
+AM_CPPFLAGS += \
+	-I$(abs_top_srcdir)/mgmt/ \
+	-I$(abs_top_srcdir)/mgmt/utils \
+	-I$(abs_top_srcdir)/proxy/http \
+	-I$(abs_top_srcdir)/proxy/hdrs \
+	-I$(abs_top_srcdir)/proxy/
+
+librpcpublichandlers_COMMON = \
+	handlers/common/RecordsUtils.cc \
+	handlers/common/RecordsUtils.h \
+	handlers/config/Configuration.cc \
+	handlers/config/Configuration.h \
+	handlers/records/Records.cc \
+	handlers/records/Records.h \
+	handlers/storage/Storage.h \
+	handlers/storage/Storage.cc \
+	handlers/server/Server.h \
+	handlers/server/Server.cc \
+	handlers/plugins/Plugins.h \
+	handlers/plugins/Plugins.cc \
+	handlers/Admin.h
+
+librpcpublichandlers_la_SOURCES = \
+    $(librpcpublichandlers_COMMON) \
+    $(shared_overridable_txn_vars_SOURCES)
+
+
+# distclean
+# This is a workaround to deal with a newer version of automake, apparently there
+# is an issue when including subdir-objects and sources outside of subtree.
+# If we include a file from another subdir as is(was) the case of overridable_txn,
+# then the distclean will try to remove the file from the original folder as well
+# from here. To overcome this issue, we create a file here that will be used
+# for building.
+# We have also added a proper cleaning for it.
+shared_overridable_txn_vars_SOURCES = overridable_txn_vars.cc
+nodist_librpcpublichandlers_la_SOURCES = $(shared_overridable_txn_vars_SOURCES)
+
+shared_rpc_ipc_client_SOURCES = IPCSocketClient.cc
+
+# This may not be needed. Ok for now.
+CLEANDIST = $(shared_overridable_txn_vars_SOURCES) $(shared_rpc_ipc_client_SOURCES)
+
+clean-local:
+	rm -f $(shared_overridable_txn_vars_SOURCES) $(shared_rpc_ipc_client_SOURCES)
+
+distclean-local:
+	rm -f $(shared_overridable_txn_vars_SOURCES) $(shared_rpc_ipc_client_SOURCES)
+
+# Build with this file instead of the original one.
+$(shared_overridable_txn_vars_SOURCES):
+	echo "#include \"$(top_builddir)/src/shared/$@\"" >$@
+
+$(shared_rpc_ipc_client_SOURCES):
+	echo "#include \"$(top_builddir)/src/shared/rpc/$@\"" >$@
+
+include $(top_srcdir)/build/tidy.mk
+
+clang-tidy-local: $(DIST_SOURCES)
+	$(CXX_Clang_Tidy)
+
diff --git a/mgmt/rpc/config/JsonRPCConfig.cc b/mgmt/rpc/config/JsonRPCConfig.cc
new file mode 100644
index 0000000..aabb8e0
--- /dev/null
+++ b/mgmt/rpc/config/JsonRPCConfig.cc
@@ -0,0 +1,103 @@
+/**
+  @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 <string>
+
+#include "JsonRPCConfig.h"
+
+#include "tscore/Diags.h"
+#include "tscore/ts_file.h"
+#include "records/I_RecCore.h"
+
+#include "rpc/jsonrpc/JsonRPCManager.h"
+
+namespace
+{
+static constexpr auto RPC_ENABLED_KEY_NAME{"enabled"};
+static constexpr auto COMM_CONFIG_KEY_UNIX{"unix"};
+} // namespace
+namespace rpc::config
+{
+void
+RPCConfig::load(YAML::Node const &params)
+{
+  try {
+    if (auto n = params[RPC_ENABLED_KEY_NAME]) {
+      _rpcEnabled = n.as<bool>();
+    } else {
+      Warning("%s not present.", RPC_ENABLED_KEY_NAME);
+    }
+
+    if (auto n = params[COMM_CONFIG_KEY_UNIX]) {
+      _commConfig       = n;
+      _selectedCommType = CommType::UNIX;
+    } else {
+      Note("%s not present.", COMM_CONFIG_KEY_UNIX);
+    }
+
+  } catch (YAML::Exception const &ex) {
+    Warning("We found an issue when reading the parameter: %s . Using defaults", ex.what());
+  }
+}
+
+YAML::Node
+RPCConfig::get_comm_config_params() const
+{
+  return _commConfig;
+}
+
+RPCConfig::CommType
+RPCConfig::get_comm_type() const
+{
+  return _selectedCommType;
+}
+
+bool
+RPCConfig::is_enabled() const
+{
+  return _rpcEnabled;
+}
+
+void
+RPCConfig::load_from_file(std::string const &filePath)
+{
+  std::error_code ec;
+  std::string content{ts::file::load(ts::file::path{filePath}, ec)};
+
+  if (ec) {
+    Warning("Cannot open the config file: %s - %s", filePath.c_str(), strerror(ec.value()));
+    // The rpc will be enabled by default with the default values.
+    return;
+  }
+
+  YAML::Node rootNode;
+  try {
+    rootNode = YAML::Load(content);
+
+    // read configured parameters.
+    if (auto rpc = rootNode["rpc"]) {
+      this->load(rpc);
+    }
+  } catch (std::exception const &ex) {
+    Warning("Something happened parsing the content of %s : %s", filePath.c_str(), ex.what());
+    return;
+  };
+}
+
+} // namespace rpc::config
diff --git a/mgmt/rpc/config/JsonRPCConfig.h b/mgmt/rpc/config/JsonRPCConfig.h
new file mode 100644
index 0000000..20e533f
--- /dev/null
+++ b/mgmt/rpc/config/JsonRPCConfig.h
@@ -0,0 +1,92 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <string_view>
+
+#include "yaml-cpp/yaml.h"
+
+namespace rpc::config
+{
+///
+/// @brief This class holds and parse all the configuration needed to run the JSONRPC server, communication implementation
+/// can use this class to feed their own configuration, though it's not mandatory as their API @see
+/// BaseCommInterface::configure uses a YAML::Node this class can be used on top of it and parse the "comm_config" from a
+/// wider file.
+///
+/// The configuration is divided into two
+/// sections:
+/// a) General RPC configuration:
+///   "communication_type" Defines the communication that should be used by the server. @see Commype
+///   "rpc_enabled" Used to set the toggle to disable or enable the whole server.
+///
+/// b) Comm specfics Configuration.
+///   "comm_config"
+///   This is defined by the specific communication, each communication can define and implement their own configuration flags. @see
+///   IPCSocketServer::Config for an example
+///
+/// Example configuration:
+///
+// rpc:
+//   enabled: true
+//   unix:
+//     lock_path_name: /tmp/conf_jsonrp
+//     sock_path_name: /tmp/conf_jsonrpc.sock
+//     backlog: 5
+//     max_retry_on_transient_errors: 64
+///
+/// All communication section should use a root node name "comm_config", @c RPCConfig will return the full node when requested @see
+/// get_comm_config_param, then it's up to the communication implementation to parse it.
+/// @note By default Unix Domain Socket will be used as a communication.
+/// @note By default the enable/disable toggle will set to Enabled.
+/// @note By default a comm_config node will be Null.
+class RPCConfig
+{
+public:
+  enum class CommType { UNIX = 1 };
+
+  RPCConfig() = default;
+
+  /// @brief Get the configured specifics for a particular tansport, all nodes under "comm_config" will be return here.
+  //  it's up to the caller to know how to parse this.
+  /// @return A YAML::Node that contains the passed configuration.
+  YAML::Node get_comm_config_params() const;
+
+  /// @brief Function that returns the configured communication type.
+  /// @return a communication type, CommType::UNIX by default.
+  CommType get_comm_type() const;
+
+  /// @brief Checks if the server was configured to be enabled or disabled. The server should be explicitly disabled by
+  ///        configuration as it is enabled by default.
+  /// @return true if enable, false if set disabled.
+  bool is_enabled() const;
+
+  /// @brief Load the configuration from the content of a file. If the file does not exist, the default values will be used.
+  void load_from_file(std::string const &filePath);
+
+  /// @brief Load configuration from a YAML::Node. This can be used to expose it as public rrc handler.
+  void load(YAML::Node const &params);
+
+private:
+  YAML::Node _commConfig;                     //!< "comm_config" section of the configuration file.
+  CommType _selectedCommType{CommType::UNIX}; //!< The selected (by configuration) communication type. 1 by default.
+  bool _rpcEnabled{true};                     //!< holds the configuration toggle value for "rpc_enable" node. Enabled by default.
+};
+} // namespace rpc::config
diff --git a/mgmt/rpc/handlers/common/ErrorUtils.h b/mgmt/rpc/handlers/common/ErrorUtils.h
new file mode 100644
index 0000000..1b17c9e
--- /dev/null
+++ b/mgmt/rpc/handlers/common/ErrorUtils.h
@@ -0,0 +1,65 @@
+/**
+   @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.
+*/
+
+#pragma once
+// TODO: we have to rename and split this file.(Errors and Errata)
+
+#include <system_error>
+#include <string_view>
+
+#include "tscore/Errata.h"
+#include "tscore/BufferWriter.h"
+
+namespace rpc::handlers::errors
+{
+// High level handler error codes, each particular handler can be fit into one of the
+// following categories.
+// enum YourOwnHandlerEnum {
+//   FOO_ERROR = Codes::SOME_CATEGORY,
+// };
+// With this we try to avoid error codes collision. You can also use same error Code for all your
+// errors.
+enum Codes : unsigned int {
+  CONFIGURATION = 1,
+  METRIC        = 1000,
+  RECORD        = 2000,
+  SERVER        = 3000,
+  STORAGE       = 4000,
+  PLUGIN        = 5000,
+  // Add more here. Give enough space between jumps.
+  GENERIC = 30000
+};
+
+static constexpr int ERRATA_DEFAULT_ID{1};
+
+template <typename... Args>
+static inline ts::Errata
+make_errata(int code, std::string_view fmt, Args &&... args)
+{
+  std::string text;
+  return ts::Errata{}.push(ERRATA_DEFAULT_ID, code, ts::bwprint(text, fmt, std::forward<Args>(args)...));
+}
+
+static inline ts::Errata
+make_errata(int code, std::string_view text)
+{
+  return ts::Errata{}.push(ERRATA_DEFAULT_ID, code, text);
+}
+} // namespace rpc::handlers::errors
\ No newline at end of file
diff --git a/mgmt/rpc/handlers/common/RecordsUtils.cc b/mgmt/rpc/handlers/common/RecordsUtils.cc
new file mode 100644
index 0000000..516cb49
--- /dev/null
+++ b/mgmt/rpc/handlers/common/RecordsUtils.cc
@@ -0,0 +1,302 @@
+/**
+   @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 "RecordsUtils.h"
+
+#include <system_error>
+#include <string>
+
+#include "convert.h"
+#include "records/P_RecCore.h"
+#include "tscore/Tokenizer.h"
+
+namespace
+{ // anonymous namespace
+
+struct RPCRecordErrorCategory : std::error_category {
+  const char *name() const noexcept override;
+  std::string message(int ev) const override;
+};
+
+const char *
+RPCRecordErrorCategory::name() const noexcept
+{
+  return "rpc_handler_record_error";
+}
+std::string
+RPCRecordErrorCategory::message(int ev) const
+{
+  switch (static_cast<rpc::handlers::errors::RecordError>(ev)) {
+  case rpc::handlers::errors::RecordError::RECORD_NOT_FOUND:
+    return {"Record not found."};
+  case rpc::handlers::errors::RecordError::RECORD_NOT_CONFIG:
+    return {"Record is not a configuration type."};
+  case rpc::handlers::errors::RecordError::RECORD_NOT_METRIC:
+    return {"Record is not a metric type."};
+  case rpc::handlers::errors::RecordError::INVALID_RECORD_NAME:
+    return {"Invalid Record Name."};
+  case rpc::handlers::errors::RecordError::VALIDITY_CHECK_ERROR:
+    return {"Validity check failed."};
+  case rpc::handlers::errors::RecordError::GENERAL_ERROR:
+    return {"Error reading the record."};
+  case rpc::handlers::errors::RecordError::RECORD_WRITE_ERROR:
+    return {"We could not write the record."};
+  case rpc::handlers::errors::RecordError::REQUESTED_TYPE_MISMATCH:
+    return {"Found record does not match the requested type"};
+  case rpc::handlers::errors::RecordError::INVALID_INCOMING_DATA:
+    return {"Invalid request data provided"};
+  default:
+    return "Record error error " + std::to_string(ev);
+  }
+}
+
+const RPCRecordErrorCategory rpcRecordErrorCategory{};
+} // anonymous namespace
+
+namespace rpc::handlers::errors
+{
+std::error_code
+make_error_code(rpc::handlers::errors::RecordError e)
+{
+  return {static_cast<int>(e), rpcRecordErrorCategory};
+}
+} // namespace rpc::handlers::errors
+
+namespace
+{
+struct Context {
+  using CbType = std::function<bool(RecT, std::error_code &)>;
+  YAML::Node yaml;
+  std::error_code ec;
+  // regex do not need to set the callback.
+  CbType checkCb;
+};
+} // namespace
+
+namespace rpc::handlers::records::utils
+{
+void static get_record_impl(std::string const &name, Context &ctx)
+{
+  auto yamlConverter = [](const RecRecord *record, void *data) {
+    auto &ctx = *static_cast<Context *>(data);
+
+    if (!record) {
+      ctx.ec = rpc::handlers::errors::RecordError::RECORD_NOT_FOUND;
+      return;
+    }
+
+    if (!ctx.checkCb(record->rec_type, ctx.ec)) {
+      // error_code in the callback will be set.
+      return;
+    }
+
+    try {
+      ctx.yaml = *record;
+    } catch (std::exception const &ex) {
+      ctx.ec = rpc::handlers::errors::RecordError::GENERAL_ERROR;
+      return;
+    }
+  };
+
+  const auto ret = RecLookupRecord(name.c_str(), yamlConverter, &ctx);
+
+  if (ctx.ec) {
+    // This will be set if the invocation of the callback inside the context have something to report, in this case
+    // we give this priority of tracking the error back to the caller.
+    return;
+  }
+
+  if (ret != REC_ERR_OKAY) {
+    ctx.ec = rpc::handlers::errors::RecordError::RECORD_NOT_FOUND;
+    return;
+  }
+}
+
+void static get_record_regex_impl(std::string const &regex, unsigned recType, Context &ctx)
+{
+  // In this case, where we lookup base on a regex, the only validation we need is base on the recType and the ability to be
+  // converted to a Yaml Node.
+  auto yamlConverter = [](const RecRecord *record, void *data) {
+    auto &ctx = *static_cast<Context *>(data);
+
+    if (!record) {
+      return;
+    }
+
+    YAML::Node recordYaml;
+
+    try {
+      recordYaml = *record;
+    } catch (std::exception const &ex) {
+      ctx.ec = rpc::handlers::errors::RecordError::GENERAL_ERROR;
+      return;
+    }
+
+    // we have to append the records to the context one.
+    ctx.yaml.push_back(recordYaml);
+  };
+
+  const auto ret = RecLookupMatchingRecords(recType, regex.c_str(), yamlConverter, &ctx);
+  // if the passed regex didn't match, it will not report any error. We will only get errors when converting
+  // the record into yaml(so far).
+  if (ctx.ec) {
+    return;
+  }
+
+  if (ret != REC_ERR_OKAY) {
+    ctx.ec = rpc::handlers::errors::RecordError::GENERAL_ERROR;
+    return;
+  }
+}
+
+// This two functions may look similar but they are not. First runs the validation in a different way.
+std::tuple<YAML::Node, std::error_code>
+get_yaml_record_regex(std::string const &name, unsigned recType)
+{
+  Context ctx;
+
+  // librecord API will use the recType to validate the type.
+  get_record_regex_impl(name, recType, ctx);
+
+  return {ctx.yaml, ctx.ec};
+}
+
+std::tuple<YAML::Node, std::error_code>
+get_yaml_record(std::string const &name, ValidateRecType check)
+{
+  Context ctx;
+
+  // Set the validation callback.
+  ctx.checkCb = check;
+
+  // librecords will use the callback we provide in the ctx.checkCb to run the validation.
+  get_record_impl(name, ctx);
+
+  return {ctx.yaml, ctx.ec};
+}
+
+// Basic functions to help setting a record value properly. All this functionality is originally from WebMgmtUtils.
+// TODO: we can work out something different.
+namespace
+{ // anonymous namespace
+  bool
+  recordRegexCheck(const char *pattern, const char *value)
+  {
+    pcre *regex;
+    const char *error;
+    int erroffset;
+
+    regex = pcre_compile(pattern, 0, &error, &erroffset, nullptr);
+    if (!regex) {
+      return false;
+    } else {
+      int r = pcre_exec(regex, nullptr, value, strlen(value), 0, 0, nullptr, 0);
+
+      pcre_free(regex);
+      return (r != -1) ? true : false;
+    }
+
+    return false; // no-op
+  }
+
+  bool
+  recordRangeCheck(const char *pattern, const char *value)
+  {
+    char *p = const_cast<char *>(pattern);
+    Tokenizer dashTok("-");
+
+    if (recordRegexCheck("^[0-9]+$", value)) {
+      while (*p != '[') {
+        p++;
+      } // skip to '['
+      if (dashTok.Initialize(++p, COPY_TOKS) == 2) {
+        int l_limit = atoi(dashTok[0]);
+        int u_limit = atoi(dashTok[1]);
+        int val     = atoi(value);
+        if (val >= l_limit && val <= u_limit) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  bool
+  recordIPCheck(const char *pattern, const char *value)
+  {
+    //  regex_t regex;
+    //  int result;
+    bool check;
+    const char *range_pattern = R"(\[[0-9]+\-[0-9]+\]\\\.\[[0-9]+\-[0-9]+\]\\\.\[[0-9]+\-[0-9]+\]\\\.\[[0-9]+\-[0-9]+\])";
+    const char *ip_pattern    = "[0-9]*[0-9]*[0-9].[0-9]*[0-9]*[0-9].[0-9]*[0-9]*[0-9].[0-9]*[0-9]*[0-9]";
+
+    Tokenizer dotTok1(".");
+    Tokenizer dotTok2(".");
+
+    check = true;
+    if (recordRegexCheck(range_pattern, pattern) && recordRegexCheck(ip_pattern, value)) {
+      if (dotTok1.Initialize(const_cast<char *>(pattern), COPY_TOKS) == 4 &&
+          dotTok2.Initialize(const_cast<char *>(value), COPY_TOKS) == 4) {
+        for (int i = 0; i < 4 && check; i++) {
+          if (!recordRangeCheck(dotTok1[i], dotTok2[i])) {
+            check = false;
+          }
+        }
+        if (check) {
+          return true;
+        }
+      }
+    } else if (strcmp(value, "") == 0) {
+      return true;
+    }
+    return false;
+  }
+} // namespace
+
+bool
+recordValidityCheck(const char *value, RecCheckT checkType, const char *pattern)
+{
+  switch (checkType) {
+  case RECC_STR:
+    if (recordRegexCheck(pattern, value)) {
+      return true;
+    }
+    break;
+  case RECC_INT:
+    if (recordRangeCheck(pattern, value)) {
+      return true;
+    }
+    break;
+  case RECC_IP:
+    if (recordIPCheck(pattern, value)) {
+      return true;
+    }
+    break;
+  case RECC_NULL:
+    // skip checking
+    return true;
+  default:
+    // unknown RecordCheckType...
+    ;
+  }
+
+  return false;
+}
+
+} // namespace rpc::handlers::records::utils
diff --git a/mgmt/rpc/handlers/common/RecordsUtils.h b/mgmt/rpc/handlers/common/RecordsUtils.h
new file mode 100644
index 0000000..0127a58
--- /dev/null
+++ b/mgmt/rpc/handlers/common/RecordsUtils.h
@@ -0,0 +1,102 @@
+/* @file
+   @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.
+*/
+
+#pragma once
+
+#include <tuple>
+
+#include "rpc/handlers/common/convert.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+
+#include "records/I_RecCore.h"
+#include "records/P_RecCore.h"
+#include "tscore/Diags.h"
+#include "tscore/Errata.h"
+
+#include <yaml-cpp/yaml.h>
+
+namespace rpc::handlers::errors
+{
+enum class RecordError {
+  RECORD_NOT_FOUND = Codes::RECORD,
+  RECORD_NOT_CONFIG,
+  RECORD_NOT_METRIC,
+  INVALID_RECORD_NAME,
+  VALIDITY_CHECK_ERROR,
+  GENERAL_ERROR,
+  RECORD_WRITE_ERROR,
+  REQUESTED_TYPE_MISMATCH,
+  INVALID_INCOMING_DATA
+};
+std::error_code make_error_code(rpc::handlers::errors::RecordError e);
+} // namespace rpc::handlers::errors
+
+namespace std
+{
+template <> struct is_error_code_enum<rpc::handlers::errors::RecordError> : true_type {
+};
+
+} // namespace std
+
+namespace rpc::handlers::records::utils
+{
+// response request constants
+inline const std::string RECORD_NAME_REGEX_KEY{"record_name_regex"};
+inline const std::string RECORD_NAME_KEY{"record_name"};
+inline const std::string RECORD_VALUE_KEY{"record_value"};
+inline const std::string RECORD_TYPES_KEY{"rec_types"};
+inline const std::string RECORD_UPDATE_TYPE_KEY{"update_type"};
+inline const std::string ERROR_CODE_KEY{"code"};
+inline const std::string ERROR_MESSAGE_KEY{"message"};
+
+using ValidateRecType = std::function<bool(RecT, std::error_code &)>;
+
+///
+/// @brief Get a Record as a YAML node
+///
+/// @param name The record name that is being requested.
+/// @param check A function @see ValidateRecType that will be used to validate that the record we want meets the expected
+/// criteria. ie: record type. Check @c RecLookupRecord API to see how it's called.
+/// @return std::tuple<YAML::Node, std::error_code>
+///
+std::tuple<YAML::Node, std::error_code> get_yaml_record(std::string const &name, ValidateRecType check);
+
+///
+/// @brief Get a Record as a YAML node using regex as name.
+///
+/// @param regex The regex that will be used to lookup records.
+/// @param recType The record type we want to match againts the retrieved records. This could be either a single value or a bitwise
+/// value.
+/// @return std::tuple<YAML::Node, std::error_code>
+///
+std::tuple<YAML::Node, std::error_code> get_yaml_record_regex(std::string const &regex, unsigned recType);
+
+///
+/// @brief Runs a validity check base on the type and the pattern.
+///
+/// @param value Value where the validity check should be applied.
+/// @param checkType The type of the value.
+/// @param pattern  The pattern.
+/// @return true if the validity was ok, false otherwise.
+///
+bool recordValidityCheck(const char *value, RecCheckT checkType,
+                         const char *pattern); // code originally from WebMgmtUtils
+
+} // namespace rpc::handlers::records::utils
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/handlers/common/Utils.h
similarity index 63%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/handlers/common/Utils.h
index 7cd5a84..309f515 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/handlers/common/Utils.h
@@ -1,4 +1,6 @@
-/*
+/* @file
+   @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
@@ -15,8 +17,25 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 */
-
 #pragma once
 
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#include <string>
+#include <yaml-cpp/yaml.h>
+
+namespace rpc::handlers::utils
+{
+inline bool
+is_true_flag(YAML::Node const &node)
+{
+  if (node.IsNull()) {
+    return false;
+  }
+  bool isTrue{false};
+  try {
+    auto str = node.as<std::string>();
+    isTrue   = str == "yes" || str == "true" || str == "1";
+  } catch (YAML::Exception const &ex) {
+  }
+  return isTrue;
+}
+} // namespace rpc::handlers::utils
\ No newline at end of file
diff --git a/mgmt/rpc/handlers/common/convert.h b/mgmt/rpc/handlers/common/convert.h
new file mode 100644
index 0000000..d74ec0b
--- /dev/null
+++ b/mgmt/rpc/handlers/common/convert.h
@@ -0,0 +1,179 @@
+/* @file
+   @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.
+*/
+#pragma once
+
+#include <iostream>
+#include <exception>
+
+#include <yaml-cpp/yaml.h>
+
+#include "records/I_RecCore.h"
+#include "records/P_RecCore.h"
+
+#include "shared/overridable_txn_vars.h"
+
+#include "RecordsUtils.h"
+
+///
+/// @brief Namespace to group all the names used for key access to the yaml lookup nodes.
+///
+///
+namespace constants_rec
+{
+static constexpr auto REC{"record"};
+
+static constexpr auto NAME{"record_name"};
+static constexpr auto RECORD_TYPE{"record_type"};
+static constexpr auto RECORD_VERSION{"version"};
+static constexpr auto REGISTERED{"registered"};
+static constexpr auto RSB{"raw_stat_block"};
+static constexpr auto ORDER{"order"};
+static constexpr auto ACCESS_TYPE{"access_type"};
+static constexpr auto UPDATE_STATUS{"update_status"};
+static constexpr auto UPDATE_TYPE{"update_type"};
+static constexpr auto CHECK_TYPE{"checktype"};
+static constexpr auto SOURCE{"source"};
+static constexpr auto CHECK_EXPR{"check_expr"};
+static constexpr auto CLASS{"record_class"};
+static constexpr auto OVERRIDABLE{"overridable"};
+static constexpr auto DATA_TYPE{"data_type"};
+static constexpr auto CURRENT_VALUE{"current_value"};
+static constexpr auto DEFAULT_VALUE{"default_value"};
+static constexpr auto CONFIG_META{"config_meta"};
+static constexpr auto STAT_META{"stat_meta"};
+
+static constexpr auto PERSIST_TYPE{"persist_type"};
+
+} // namespace constants_rec
+
+namespace YAML
+{
+///
+/// @brief specialize convert template class for RecPersistT
+///
+template <> struct convert<RecPersistT> {
+  static Node
+  encode(const RecPersistT &type)
+  {
+    return Node{static_cast<int>(type)};
+  }
+};
+
+///
+/// @brief specialize convert template class for RecConfigMeta
+///
+template <> struct convert<RecConfigMeta> {
+  static Node
+  encode(const RecConfigMeta &configMeta)
+  {
+    Node node;
+    // TODO: do we really want each specific encode implementation for each enum type?
+    node[constants_rec::ACCESS_TYPE]   = static_cast<int>(configMeta.access_type);
+    node[constants_rec::UPDATE_STATUS] = static_cast<int>(configMeta.update_required);
+    node[constants_rec::UPDATE_TYPE]   = static_cast<int>(configMeta.update_type);
+    node[constants_rec::CHECK_TYPE]    = static_cast<int>(configMeta.check_type);
+    node[constants_rec::SOURCE]        = static_cast<int>(configMeta.source);
+    node[constants_rec::CHECK_EXPR]    = configMeta.check_expr ? configMeta.check_expr : "null";
+
+    return node;
+  }
+};
+
+///
+/// @brief specialize convert template class for RecStatMeta
+///
+template <> struct convert<RecStatMeta> {
+  static Node
+  encode(const RecStatMeta &statMeta)
+  {
+    // TODO. just make sure that we know which data should be included here.
+    Node node;
+    node[constants_rec::PERSIST_TYPE] = statMeta.persist_type;
+    return node;
+  }
+};
+
+///
+/// @brief specialize convert template class for RecRecord
+///
+template <> struct convert<RecRecord> {
+  static Node
+  encode(const RecRecord &record)
+  {
+    Node node;
+    try {
+      node[constants_rec::NAME]           = record.name ? record.name : "null";
+      node[constants_rec::RECORD_TYPE]    = static_cast<int>(record.data_type);
+      node[constants_rec::RECORD_VERSION] = record.version;
+      node[constants_rec::REGISTERED]     = record.registered;
+      node[constants_rec::RSB]            = record.rsb_id;
+      node[constants_rec::ORDER]          = record.order;
+
+      if (REC_TYPE_IS_CONFIG(record.rec_type)) {
+        node[constants_rec::CONFIG_META] = record.config_meta;
+      } else if (REC_TYPE_IS_STAT(record.rec_type)) {
+        node[constants_rec::STAT_META] = record.stat_meta;
+      }
+
+      node[constants_rec::CLASS] = static_cast<int>(record.rec_type);
+
+      if (record.name) {
+        const auto it                    = ts::Overridable_Txn_Vars.find(record.name);
+        node[constants_rec::OVERRIDABLE] = (it == ts::Overridable_Txn_Vars.end()) ? "false" : "true";
+      }
+
+      switch (record.data_type) {
+      case RECD_INT:
+        node[constants_rec::DATA_TYPE]     = "INT";
+        node[constants_rec::CURRENT_VALUE] = record.data.rec_int;
+        node[constants_rec::DEFAULT_VALUE] = record.data_default.rec_int;
+        break;
+      case RECD_FLOAT:
+        node[constants_rec::DATA_TYPE]     = "FLOAT";
+        node[constants_rec::CURRENT_VALUE] = record.data.rec_float;
+        node[constants_rec::DEFAULT_VALUE] = record.data_default.rec_float;
+        break;
+      case RECD_STRING:
+        node[constants_rec::DATA_TYPE]     = "STRING";
+        node[constants_rec::CURRENT_VALUE] = record.data.rec_string ? record.data.rec_string : "null";
+        node[constants_rec::DEFAULT_VALUE] = record.data_default.rec_string ? record.data_default.rec_string : "null";
+        break;
+      case RECD_COUNTER:
+        node[constants_rec::DATA_TYPE]     = "COUNTER";
+        node[constants_rec::CURRENT_VALUE] = record.data.rec_counter;
+        node[constants_rec::DEFAULT_VALUE] = record.data_default.rec_counter;
+        break;
+      default:
+        // this is an error, internal we should flag it
+        break;
+      }
+    } catch (std::exception const &e) {
+      // we create an empty map node, we do not want to have a null. revisit this.
+      YAML::NodeType::value kind = YAML::NodeType::Map;
+      node                       = YAML::Node{kind};
+    }
+
+    Node yrecord;
+    yrecord[constants_rec::REC] = node;
+    return yrecord;
+  }
+};
+
+} // namespace YAML
\ No newline at end of file
diff --git a/mgmt/rpc/handlers/config/Configuration.cc b/mgmt/rpc/handlers/config/Configuration.cc
new file mode 100644
index 0000000..f3c26ef
--- /dev/null
+++ b/mgmt/rpc/handlers/config/Configuration.cc
@@ -0,0 +1,202 @@
+/*
+  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 "Configuration.h"
+
+#include <system_error>
+#include <string>
+#include <string_view>
+
+#include "tscore/BufferWriter.h"
+#include "records/I_RecCore.h"
+#include "records/P_RecCore.h"
+#include "tscore/Diags.h"
+
+#include "config/FileManager.h"
+
+#include "rpc/handlers/common/RecordsUtils.h"
+
+namespace utils = rpc::handlers::records::utils;
+
+namespace
+{
+/// key value pair from each element passed in the set command.
+struct SetRecordCmdInfo {
+  std::string name;
+  std::string value;
+};
+} // namespace
+
+namespace YAML
+{
+template <> struct convert<SetRecordCmdInfo> {
+  static bool
+  decode(Node const &node, SetRecordCmdInfo &info)
+  {
+    if (!node[utils::RECORD_NAME_KEY] || !node[utils::RECORD_VALUE_KEY]) {
+      return false;
+    }
+
+    info.name  = node[utils::RECORD_NAME_KEY].as<std::string>();
+    info.value = node[utils::RECORD_VALUE_KEY].as<std::string>();
+    return true;
+  }
+};
+} // namespace YAML
+
+namespace rpc::handlers::config
+{
+namespace err   = rpc::handlers::errors;
+namespace utils = rpc::handlers::records::utils;
+
+namespace
+{
+  template <typename T>
+  bool
+  set_data_type(SetRecordCmdInfo const &info)
+  {
+    if constexpr (std::is_same_v<T, float>) {
+      T val;
+      try {
+        val = std::stof(info.value);
+      } catch (std::exception const &ex) {
+        return false;
+      }
+      // set the value
+      if (RecSetRecordFloat(info.name.c_str(), val, REC_SOURCE_DEFAULT) != REC_ERR_OKAY) {
+        return false;
+      }
+    } else if constexpr (std::is_same_v<T, int>) {
+      T val;
+      try {
+        val = std::stoi(info.value);
+      } catch (std::exception const &ex) {
+        return false;
+      }
+      // set the value
+      if (RecSetRecordInt(info.name.c_str(), val, REC_SOURCE_DEFAULT) != REC_ERR_OKAY) {
+        return false;
+      }
+    } else if constexpr (std::is_same_v<T, std::string>) {
+      if (RecSetRecordString(info.name.c_str(), const_cast<char *>(info.value.c_str()), REC_SOURCE_DEFAULT) != REC_ERR_OKAY) {
+        return false;
+      }
+    }
+
+    // all set.
+    return true;
+  }
+} // namespace
+
+ts::Rv<YAML::Node>
+set_config_records(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+
+  // we need the type and the udpate type for now.
+  using LookupContext = std::tuple<RecDataT, RecCheckT, const char *, RecUpdateT>;
+
+  for (auto const &kv : params) {
+    SetRecordCmdInfo info;
+    try {
+      info = kv.as<SetRecordCmdInfo>();
+    } catch (YAML::Exception const &ex) {
+      resp.errata().push({err::RecordError::RECORD_NOT_FOUND});
+      continue;
+    }
+
+    LookupContext recordCtx;
+
+    // Get record info first. TODO: we may just want to get the full record and  then send it back  as a response.
+    const auto ret = RecLookupRecord(
+      info.name.c_str(),
+      [](const RecRecord *record, void *data) {
+        auto &[dataType, checkType, pattern, updateType] = *static_cast<LookupContext *>(data);
+        if (REC_TYPE_IS_CONFIG(record->rec_type)) {
+          dataType  = record->data_type;
+          checkType = record->config_meta.check_type;
+          if (record->config_meta.check_expr) {
+            pattern = record->config_meta.check_expr;
+          }
+          updateType = record->config_meta.update_type;
+        }
+      },
+      &recordCtx);
+
+    // make sure if exist. If not, we stop it and do not keep forward.
+    if (ret != REC_ERR_OKAY) {
+      resp.errata().push({err::RecordError::RECORD_NOT_FOUND});
+      continue;
+    }
+
+    // now set the value.
+    auto const &[dataType, checkType, pattern, updateType] = recordCtx;
+
+    // run the check only if we have something to check against it.
+    if (pattern != nullptr && utils::recordValidityCheck(info.value.c_str(), checkType, pattern) == false) {
+      resp.errata().push({err::RecordError::VALIDITY_CHECK_ERROR});
+      continue;
+    }
+
+    bool set_ok{false};
+    switch (dataType) {
+    case RECD_INT:
+    case RECD_COUNTER:
+      set_ok = set_data_type<int>(info);
+      break;
+    case RECD_FLOAT:
+      set_ok = set_data_type<float>(info);
+      break;
+    case RECD_STRING:
+      set_ok = set_data_type<std::string>(info);
+      break;
+    default:;
+    }
+
+    if (set_ok) {
+      YAML::Node updatedRecord;
+      updatedRecord[utils::RECORD_NAME_KEY]        = info.name;
+      updatedRecord[utils::RECORD_UPDATE_TYPE_KEY] = std::to_string(updateType);
+      resp.result().push_back(updatedRecord);
+    } else {
+      resp.errata().push({err::RecordError::GENERAL_ERROR});
+      continue;
+    }
+  }
+
+  return resp;
+}
+
+ts::Rv<YAML::Node>
+reload_config(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+  Debug("RPC", "invoke plugin callbacks");
+  // if there is any error, report it back.
+  if (auto err = FileManager::instance().rereadConfig(); err.size()) {
+    resp = err;
+  }
+  // If any callback was register(TSMgmtUpdateRegister) for config notifications, then it will be eventually notify.
+  FileManager::instance().invokeConfigPluginCallbacks();
+  // save config time.
+  RecSetRecordInt("proxy.node.config.reconfigure_time", time(nullptr), REC_SOURCE_DEFAULT);
+  // TODO: we may not need this any more
+  RecSetRecordInt("proxy.node.config.reconfigure_required", 0, REC_SOURCE_DEFAULT);
+
+  return resp;
+}
+} // namespace rpc::handlers::config
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/handlers/config/Configuration.h
similarity index 69%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/handlers/config/Configuration.h
index 7cd5a84..4a3f2f2 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/handlers/config/Configuration.h
@@ -1,4 +1,6 @@
-/*
+/* @file
+   @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
@@ -18,5 +20,11 @@
 
 #pragma once
 
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#include "rpc/jsonrpc/JsonRPCManager.h"
+
+namespace rpc::handlers::config
+{
+ts::Rv<YAML::Node> set_config_records(std::string_view const &id, YAML::Node const &params);
+ts::Rv<YAML::Node> reload_config(std::string_view const &id, YAML::Node const &params);
+
+} // namespace rpc::handlers::config
\ No newline at end of file
diff --git a/mgmt/rpc/handlers/plugins/Plugins.cc b/mgmt/rpc/handlers/plugins/Plugins.cc
new file mode 100644
index 0000000..285d3a6
--- /dev/null
+++ b/mgmt/rpc/handlers/plugins/Plugins.cc
@@ -0,0 +1,85 @@
+/*
+  @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 "Plugins.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+
+#include "InkAPIInternal.h"
+
+namespace
+{
+const std::string PLUGIN_TAG_KEY{"tag"};
+const std::string PLUGIN_DATA_KEY{"data"};
+static constexpr auto logTag{"rpc.plugins"};
+
+struct PluginMsgInfo {
+  std::string data;
+  std::string tag;
+};
+} // namespace
+namespace YAML
+{
+template <> struct convert<PluginMsgInfo> {
+  static bool
+  decode(Node const &node, PluginMsgInfo &msg)
+  {
+    if (!node[PLUGIN_TAG_KEY] || !node[PLUGIN_DATA_KEY]) {
+      return false;
+    }
+    msg.tag  = node[PLUGIN_TAG_KEY].as<std::string>();
+    msg.data = node[PLUGIN_DATA_KEY].as<std::string>();
+
+    return true;
+  }
+};
+} // namespace YAML
+
+namespace rpc::handlers::plugins
+{
+namespace err = rpc::handlers::errors;
+
+ts::Rv<YAML::Node>
+plugin_send_basic_msg(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+  try {
+    // keep the data.
+    PluginMsgInfo info = params.as<PluginMsgInfo>();
+
+    TSPluginMsg msg;
+    msg.tag       = info.tag.c_str();
+    msg.data      = info.data.data();
+    msg.data_size = info.data.size();
+
+    APIHook *hook = lifecycle_hooks->get(TS_LIFECYCLE_MSG_HOOK);
+
+    while (hook) {
+      TSPluginMsg tmp(msg); // Just to make sure plugins don't mess this up for others.
+      hook->invoke(TS_EVENT_LIFECYCLE_MSG, &tmp);
+      hook = hook->next();
+    }
+  } catch (std::exception const &ex) {
+    Debug(logTag, "Invalid params %s", ex.what());
+    resp = err::make_errata(err::Codes::PLUGIN, "Error parsing the incoming data: {}", ex.what());
+  }
+
+  return resp;
+}
+} // namespace rpc::handlers::plugins
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/handlers/plugins/Plugins.h
similarity index 76%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/handlers/plugins/Plugins.h
index 7cd5a84..e82c2d0 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/handlers/plugins/Plugins.h
@@ -1,4 +1,6 @@
 /*
+   @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
@@ -18,5 +20,9 @@
 
 #pragma once
 
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#include "rpc/jsonrpc/JsonRPCManager.h"
+
+namespace rpc::handlers::plugins
+{
+ts::Rv<YAML::Node> plugin_send_basic_msg(std::string_view const &id, YAML::Node const &params);
+} // namespace rpc::handlers::plugins
diff --git a/mgmt/rpc/handlers/records/Records.cc b/mgmt/rpc/handlers/records/Records.cc
new file mode 100644
index 0000000..5264594
--- /dev/null
+++ b/mgmt/rpc/handlers/records/Records.cc
@@ -0,0 +1,299 @@
+/**
+   @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 "Records.h"
+
+#include <system_error>
+#include <string>
+#include <string_view>
+
+#include "handlers/common/RecordsUtils.h"
+// #include "common/yaml/codecs.h"
+///
+/// @brief Local definitions to map requests and responsponses(not fully supported yet) to custom structures. All this definitions
+/// are used during decoding and encoding of the  RPC requests.
+///
+namespace utils = rpc::handlers::records::utils;
+namespace
+{
+const std::string RECORD_LIST_KEY{"recordList"};
+const std::string ERROR_LIST_KEY{"errorList"};
+/// @brief This class maps the incoming rpc record request in general. This should be used to handle all the data around the
+/// record requests.
+///
+struct RequestRecordElement {
+  std::string recName;       //!< Incoming record name, this is used for a regex as well.
+  bool isRegex{false};       //!< set to true if the lookup should be done by using a regex instead a full name.
+  std::vector<int> recTypes; //!< incoming rec_types
+
+  /// @brief test if the requests is intended to use a regex.
+  bool
+  is_regex_req() const
+  {
+    return isRegex;
+  }
+};
+
+///
+/// @brief Class used to wrap non recoverable lookup errors during a lookup call, this errors will then, be pushed inside the
+/// errorList nodes.
+///
+struct ErrorInfo {
+  ErrorInfo(int _code)
+    : code(_code) //!< recordName and message should be set manually, unless it's created from an @c std::error_code
+  {
+  }
+  // Build it from a @c std::error_code
+  ErrorInfo(std::error_code ec) : code(ec.value()), message(ec.message()) {}
+  int code; //!< Error code, it's not mandatory to include the message if we have the code instead. The message can be found in
+            // the documentation if the code is returned.
+  std::string recordName; //!< record name may not be available in some cases, instead we can use a message. Message will use
+                          // their own field name.
+  std::string message;
+};
+
+} // namespace
+// using namespace rpc::codec::types;
+// YAML Converter for the incoming record request @see RequestRecordElement. Make sure you protect this by try/catch. We may get
+// some invalid types.
+namespace YAML
+{
+// using namespace rpc::codec::types;
+template <> struct convert<RequestRecordElement> {
+  static bool
+  decode(Node const &node, RequestRecordElement &info)
+  {
+    if (!node[utils::RECORD_NAME_REGEX_KEY] && !node[utils::RECORD_NAME_KEY]) {
+      // if we don't get any specific name, seems a bit risky to send them all back. At least some * would be nice.
+      return false;
+    }
+
+    // if both are provided, we can't proceed.
+    if (node[utils::RECORD_NAME_REGEX_KEY] && node[utils::RECORD_NAME_KEY]) {
+      return false;
+    }
+
+    // TODO: Add "type" paramater to just say, `config`, `metric`. May be handier.
+
+    if (auto n = node[utils::RECORD_TYPES_KEY]) {
+      // if it's empty should be ok, will get all of them.
+      if (n && n.IsSequence()) {
+        auto const &passedTypes = n.as<std::vector<int>>();
+        for (auto rt : passedTypes) {
+          switch (rt) {
+          case RECT_NULL:
+          case RECT_CONFIG:
+          case RECT_PROCESS:
+          case RECT_NODE:
+          case RECT_LOCAL:
+          case RECT_PLUGIN:
+          case RECT_ALL:
+            info.recTypes.push_back(rt);
+            break;
+          default:
+            // this field allows 1x1 match from the enum.
+            // we may accept the bitwise being passed as param in the future.
+            return false;
+          }
+        }
+      }
+    }
+
+    if (auto n = node[utils::RECORD_NAME_REGEX_KEY]) {
+      info.recName = n.as<std::string>();
+      info.isRegex = true;
+    } else {
+      info.recName = node[utils::RECORD_NAME_KEY].as<std::string>();
+      info.isRegex = false;
+    }
+
+    return true;
+  }
+};
+
+template <> struct convert<ErrorInfo> {
+  static Node
+  encode(ErrorInfo const &errorInfo)
+  {
+    Node errorInfoNode;
+    errorInfoNode[utils::ERROR_CODE_KEY] = errorInfo.code;
+    if (!errorInfo.message.empty()) {
+      errorInfoNode[utils::ERROR_MESSAGE_KEY] = errorInfo.message;
+    }
+    if (!errorInfo.recordName.empty()) {
+      errorInfoNode[utils::RECORD_NAME_KEY] = errorInfo.recordName;
+    }
+
+    return errorInfoNode;
+  }
+};
+} // namespace YAML
+
+namespace
+{
+static unsigned
+bitwise(std::vector<int> const &values)
+{
+  unsigned recType = RECT_ALL;
+  if (values.size() > 0) {
+    auto it = std::begin(values);
+
+    recType = *it;
+    ++it;
+    for (; it != std::end(values); ++it) {
+      recType |= *it;
+    }
+  }
+
+  return recType;
+}
+
+namespace utils = rpc::handlers::records::utils;
+namespace err   = rpc::handlers::errors;
+
+static auto
+find_record_by_name(RequestRecordElement const &element)
+{
+  unsigned recType = bitwise(element.recTypes);
+
+  return utils::get_yaml_record(element.recName, [recType](RecT rec_type, std::error_code &ec) {
+    if ((recType & rec_type) == 0) {
+      ec = err::RecordError::REQUESTED_TYPE_MISMATCH;
+      return false;
+    }
+    return true;
+  });
+}
+
+static auto
+find_records_by_regex(RequestRecordElement const &element)
+{
+  unsigned recType = bitwise(element.recTypes);
+
+  return utils::get_yaml_record_regex(element.recName, recType);
+}
+
+static auto
+find_records(RequestRecordElement const &element)
+{
+  if (element.is_regex_req()) {
+    return find_records_by_regex(element);
+  }
+  return find_record_by_name(element);
+}
+
+} // namespace
+
+namespace rpc::handlers::records
+{
+namespace err = rpc::handlers::errors;
+
+ts::Rv<YAML::Node>
+lookup_records(std::string_view const &id, YAML::Node const &params)
+{
+  // TODO: we may want to deal with our own object instead of a node here.
+  YAML::Node recordList{YAML::NodeType::Sequence}, errorList{YAML::NodeType::Sequence};
+
+  for (auto &&node : params) {
+    RequestRecordElement recordElement;
+    try {
+      recordElement = node.as<RequestRecordElement>();
+    } catch (YAML::Exception const &) {
+      errorList.push_back(ErrorInfo{{err::RecordError::INVALID_INCOMING_DATA}});
+      continue;
+    }
+
+    auto &&[recordNode, error] = find_records(recordElement);
+
+    if (error) {
+      ErrorInfo ei{error};
+      ei.recordName = recordElement.recName;
+
+      errorList.push_back(ei);
+      continue;
+    }
+
+    // Regex lookup, will get us back a sequence, of nodes. In this case we will add them one by 1 so we get a list of objects and
+    // not a sequence inside the result object, this can be changed ofc but for now this is fine.
+    if (recordNode.IsSequence()) {
+      for (auto &&n : recordNode) {
+        recordList.push_back(std::move(n));
+      }
+    } else if (recordNode.IsMap()) {
+      recordList.push_back(std::move(recordNode));
+    }
+  }
+
+  YAML::Node resp;
+  // Even if the records/errors are an empty list, we want them in the response.
+  resp[RECORD_LIST_KEY] = recordList;
+  resp[ERROR_LIST_KEY]  = errorList;
+  return resp;
+}
+
+ts::Rv<YAML::Node>
+clear_all_metrics_records(std::string_view const &id, YAML::Node const &params)
+{
+  using namespace rpc::handlers::records::utils;
+  ts::Rv<YAML::Node> resp;
+  if (RecResetStatRecord(RECT_NULL, true) != REC_ERR_OKAY) {
+    return ts::Errata{rpc::handlers::errors::RecordError::RECORD_WRITE_ERROR};
+  }
+
+  return resp;
+}
+
+ts::Rv<YAML::Node>
+clear_metrics_records(std::string_view const &id, YAML::Node const &params)
+{
+  using namespace rpc::handlers::records::utils;
+
+  YAML::Node resp, errorList;
+
+  for (auto &&element : params) {
+    RequestRecordElement recordElement;
+    try {
+      recordElement = element.as<RequestRecordElement>();
+    } catch (YAML::Exception const &) {
+      errorList.push_back(ErrorInfo{{err::RecordError::INVALID_INCOMING_DATA}});
+      continue;
+    }
+
+    if (!recordElement.recName.empty()) {
+      if (RecResetStatRecord(recordElement.recName.c_str()) != REC_ERR_OKAY) {
+        // This could be due the fact that the record is already cleared or the metric does not have any significant
+        // value.
+        ErrorInfo ei{err::RecordError::RECORD_WRITE_ERROR};
+        ei.recordName = recordElement.recName;
+        errorList.push_back(ei);
+      }
+    } else {
+      errorList.push_back(ErrorInfo{{err::RecordError::INVALID_INCOMING_DATA}});
+      continue;
+    }
+  }
+
+  if (!errorList.IsNull()) {
+    resp[ERROR_LIST_KEY] = errorList;
+  }
+
+  return resp;
+}
+
+} // namespace rpc::handlers::records
diff --git a/mgmt/rpc/handlers/records/Records.h b/mgmt/rpc/handlers/records/Records.h
new file mode 100644
index 0000000..40fd969
--- /dev/null
+++ b/mgmt/rpc/handlers/records/Records.h
@@ -0,0 +1,60 @@
+/**
+   @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.
+*/
+
+#pragma once
+
+#include <string_view>
+#include <yaml-cpp/yaml.h>
+#include "tscore/Errata.h"
+
+namespace rpc::handlers::records
+{
+///
+/// @brief Record lookups. This is a RPC function handler that retrieves a YAML::Node which will contain the result of a records
+/// lookup. @see RecRecord.
+/// Incoming parameter is expected to be a sequence, params will be converted to a @see RequestRecordElement
+/// and the response will be a YAML node that contains the findings base on the query type. @see RequestRecordElement recTypes will
+/// lead the search.
+/// @param id JSONRPC client's id.
+/// @param params lookup_records query structure.
+/// @return ts::Rv<YAML::Node> A node or an error. If ok, the node will hold the @c "recordList" sequence with the findings. In case
+/// of any missed search, ie: when paseed types didn't match the found record(s), the particular error will be added to the @c
+/// "errorList" field.
+///
+ts::Rv<YAML::Node> lookup_records(std::string_view const &id, YAML::Node const &params);
+
+///
+/// @brief A RPC function handler that clear all the metrics.
+///
+/// @param id JSONRPC client's id.
+/// @param params Nothing, this will be ignored.
+/// @return ts::Rv<YAML::Node> An empty YAML::Node or the proper Errata with the tracked error.
+///
+ts::Rv<YAML::Node> clear_all_metrics_records(std::string_view const &id, YAML::Node const &);
+
+///
+/// @brief A RPC  function  handler that clear a specific set of metrics.
+/// The @c "errorList" field will only be set if there is any error cleaning a specific metric.
+/// @param id JSONRPC client's id.
+/// @param params A list of records to update. @see RequestRecordElement
+/// @return ts::Rv<YAML::Node> A YAML::Node or the proper Errata with the tracked error.
+///
+ts::Rv<YAML::Node> clear_metrics_records(std::string_view const &id, YAML::Node const &params);
+} // namespace rpc::handlers::records
diff --git a/mgmt/rpc/handlers/server/Server.cc b/mgmt/rpc/handlers/server/Server.cc
new file mode 100644
index 0000000..982bdba
--- /dev/null
+++ b/mgmt/rpc/handlers/server/Server.cc
@@ -0,0 +1,120 @@
+/**
+   @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 "Server.h"
+
+#include "P_Cache.h"
+#include <tscore/TSSystemState.h>
+#include "rpc/handlers/common/ErrorUtils.h"
+#include "rpc/handlers/common/Utils.h"
+
+namespace rpc::handlers::server
+{
+namespace field_names
+{
+  static constexpr auto NEW_CONNECTIONS{"no_new_connections"};
+} // namespace field_names
+
+struct DrainInfo {
+  bool noNewConnections{false};
+};
+} // namespace rpc::handlers::server
+namespace YAML
+{
+template <> struct convert<rpc::handlers::server::DrainInfo> {
+  static bool
+  decode(const Node &node, rpc::handlers::server::DrainInfo &rhs)
+  {
+    namespace field = rpc::handlers::server::field_names;
+    namespace utils = rpc::handlers::utils;
+    if (!node.IsMap()) {
+      return false;
+    }
+    // optional
+    if (auto n = node[field::NEW_CONNECTIONS]; utils::is_true_flag(n)) {
+      rhs.noNewConnections = true;
+    }
+    return true;
+  }
+};
+} // namespace YAML
+
+namespace rpc::handlers::server
+{
+namespace err = rpc::handlers::errors;
+
+static bool
+is_server_draining()
+{
+  RecInt draining = 0;
+  if (RecGetRecordInt("proxy.node.config.draining", &draining) != REC_ERR_OKAY) {
+    return false;
+  }
+  return draining != 0;
+}
+
+static void inline set_server_drain(bool drain)
+{
+  TSSystemState::drain(drain);
+  RecSetRecordInt("proxy.node.config.draining", TSSystemState::is_draining() ? 1 : 0, REC_SOURCE_DEFAULT);
+}
+
+ts::Rv<YAML::Node>
+server_start_drain(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+  try {
+    if (!params.IsNull()) {
+      DrainInfo di = params.as<DrainInfo>();
+      Debug("rpc.server", "draining - No new connections %s", (di.noNewConnections ? "yes" : "no"));
+      // TODO: no new connections flag -  implement with the right metric / unimplemented in traffic_ctl
+    }
+
+    if (!is_server_draining()) {
+      set_server_drain(true);
+    } else {
+      resp.errata().push(err::make_errata(err::Codes::SERVER, "Server already draining."));
+    }
+  } catch (std::exception const &ex) {
+    Debug("rpc.handler.server", "Got an error DrainInfo decoding: %s", ex.what());
+    resp.errata().push(err::make_errata(err::Codes::SERVER, "Error found during server drain: {}", ex.what()));
+  }
+  return resp;
+}
+
+ts::Rv<YAML::Node>
+server_stop_drain(std::string_view const &id, [[maybe_unused]] YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+  if (is_server_draining()) {
+    set_server_drain(false);
+  } else {
+    resp.errata().push(err::make_errata(err::Codes::SERVER, "Server is not draining."));
+  }
+
+  return resp;
+}
+
+void
+server_shutdown(YAML::Node const &)
+{
+  sync_cache_dir_on_shutdown();
+}
+} // namespace rpc::handlers::server
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/handlers/server/Server.h
similarity index 67%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/handlers/server/Server.h
index 7cd5a84..b71d87a 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/handlers/server/Server.h
@@ -1,4 +1,6 @@
-/*
+/* @file
+   @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
@@ -18,5 +20,11 @@
 
 #pragma once
 
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#include "rpc/jsonrpc/JsonRPCManager.h"
+
+namespace rpc::handlers::server
+{
+ts::Rv<YAML::Node> server_start_drain(std::string_view const &id, YAML::Node const &params);
+ts::Rv<YAML::Node> server_stop_drain(std::string_view const &id, YAML::Node const &);
+void server_shutdown(YAML::Node const &);
+} // namespace rpc::handlers::server
diff --git a/mgmt/rpc/handlers/storage/Storage.cc b/mgmt/rpc/handlers/storage/Storage.cc
new file mode 100644
index 0000000..34d6fb2
--- /dev/null
+++ b/mgmt/rpc/handlers/storage/Storage.cc
@@ -0,0 +1,102 @@
+/**
+   @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 "Storage.h"
+#include "tscore/BufferWriter.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+#include "P_Cache.h"
+
+namespace rpc::handlers::storage::field_names
+{
+static constexpr auto PATH{"path"};
+static constexpr auto STATUS{"status"};
+static constexpr auto ERRORS{"error_count"};
+} // namespace rpc::handlers::storage::field_names
+
+namespace YAML
+{
+template <> struct convert<CacheDisk> {
+  static Node
+  encode(CacheDisk const &cdisk)
+  {
+    namespace field = rpc::handlers::storage::field_names;
+    Node node;
+    try {
+      node[field::PATH]   = cdisk.path;
+      node[field::STATUS] = (cdisk.online ? "online" : "offline");
+      node[field::ERRORS] = cdisk.num_errors;
+    } catch (std::exception const &e) {
+      return node;
+    }
+
+    Node cacheDiskNode;
+    cacheDiskNode["cachedisk"] = node;
+    return cacheDiskNode;
+  }
+};
+
+} // namespace YAML
+
+namespace rpc::handlers::storage
+{
+namespace err = rpc::handlers::errors;
+
+ts::Rv<YAML::Node>
+set_storage_offline(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+
+  for (auto &&it : params) {
+    std::string device = it.as<std::string>();
+    CacheDisk *d       = cacheProcessor.find_by_path(device.c_str(), (device.size()));
+
+    if (d) {
+      Debug("rpc.server", "Marking %s offline", device.c_str());
+
+      YAML::Node n;
+      auto ret                     = cacheProcessor.mark_storage_offline(d, /* admin */ true);
+      n["path"]                    = device;
+      n["has_online_storage_left"] = ret ? "true" : "false";
+      resp.result().push_back(std::move(n));
+    } else {
+      resp.errata().push(err::make_errata(err::Codes::STORAGE, "Passed device:'{}' does not match any defined storage", device));
+    }
+  }
+  return resp;
+}
+
+ts::Rv<YAML::Node>
+get_storage_status(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+
+  for (auto &&it : params) {
+    std::string device = it.as<std::string>();
+    CacheDisk *d       = cacheProcessor.find_by_path(device.c_str(), static_cast<int>(device.size()));
+
+    if (d) {
+      resp.result().push_back(*d);
+    } else {
+      resp.errata().push(err::make_errata(err::Codes::STORAGE, "Passed device:'{}' does not match any defined storage", device));
+    }
+  }
+  return resp;
+}
+} // namespace rpc::handlers::storage
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/handlers/storage/Storage.h
similarity index 69%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/handlers/storage/Storage.h
index 7cd5a84..1b9eba3 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/handlers/storage/Storage.h
@@ -1,4 +1,6 @@
-/*
+/* @file
+   @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
@@ -18,5 +20,10 @@
 
 #pragma once
 
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#include "rpc/jsonrpc/JsonRPCManager.h"
+
+namespace rpc::handlers::storage
+{
+ts::Rv<YAML::Node> set_storage_offline(std::string_view const &id, YAML::Node const &params);
+ts::Rv<YAML::Node> get_storage_status(std::string_view const &id, YAML::Node const &params);
+} // namespace rpc::handlers::storage
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/jsonrpc/Context.cc
similarity index 65%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/jsonrpc/Context.cc
index 7cd5a84..a0a3793 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/jsonrpc/Context.cc
@@ -1,4 +1,6 @@
-/*
+/**
+  @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
@@ -15,8 +17,19 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 */
+#include "Context.h"
 
-#pragma once
-
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+namespace rpc
+{
+// --- Call Context impl
+ts::Errata
+Context::Auth::is_blocked(TSRPCHandlerOptions const &options) const
+{
+  ts::Errata out;
+  // check every registered callback and see if they have something to say. Then report back to the manager
+  for (auto &&check : _checkers) {
+    check(options, out);
+  }
+  return out;
+};
+} // namespace rpc
diff --git a/mgmt/rpc/jsonrpc/Context.h b/mgmt/rpc/jsonrpc/Context.h
new file mode 100644
index 0000000..fb78d33
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/Context.h
@@ -0,0 +1,77 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <vector>
+#include <functional>
+#include <string_view>
+
+#include "tscore/Errata.h"
+#include "ts/apidefs.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+
+namespace rpc
+{
+constexpr bool RESTRICTED_API{true};
+constexpr bool NON_RESTRICTED_API{false};
+///
+/// @brief RPC call context class.
+///
+/// This class is used to carry information from the transport logic to the rpc invocation logic, the transport may need to block
+/// some rpc handlers from being executed which at the time of finish ups reading the raw message is yet too early to know the
+/// actual handler.
+///
+class Context
+{
+  using checker_cb = std::function<void(TSRPCHandlerOptions const &, ts::Errata &)>;
+  /// @brief Internal class to hold the permission checker part.
+  struct Auth {
+    /// Checks for permissions. This function checks for every registered permission checker.
+    ///
+    /// @param options Registered handler options.
+    /// @return ts::Errata The errata will be filled by each of the registered checkers, if there was any issue validating the
+    ///                    call, then the errata reflects that.
+    ts::Errata is_blocked(TSRPCHandlerOptions const &options) const;
+
+    /// Add permission checkers.
+    template <typename F>
+    void
+    add_checker(F &&f)
+    {
+      _checkers.emplace_back(std::forward<F>(f));
+    }
+
+  private:
+    std::vector<checker_cb> _checkers; ///< cb collection.
+  } _auth;
+
+public:
+  Auth &
+  get_auth()
+  {
+    return _auth;
+  }
+  Auth const &
+  get_auth() const
+  {
+    return _auth;
+  }
+};
+} // namespace rpc
diff --git a/mgmt/rpc/jsonrpc/Defs.h b/mgmt/rpc/jsonrpc/Defs.h
new file mode 100644
index 0000000..f528ad1
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/Defs.h
@@ -0,0 +1,189 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <variant>
+#include <string>
+#include <optional>
+
+#include <yaml-cpp/yaml.h>
+
+#include "rpc/jsonrpc/error/RPCError.h"
+
+// This file contains all the internal types used by the RPC engine to deal with all the messages
+// While we use yamlcpp for parsing, internally we model the request/response on our wrappers (RPCRequest, RPCResponse)
+namespace rpc::specs
+{
+const std::string JSONRPC_VERSION{"2.0"};
+
+/// @brief  This class encapsulate the registered handler call data.
+/// It contains the YAML::Node that will contain the response of a call and  if any error, will also encapsulate the  error from the
+/// call.
+/// @see MethodHandler
+class RPCHandlerResponse
+{
+public:
+  YAML::Node result; //!< The response from the registered handler.
+  ts::Errata errata; //!< The  error response from the registered handler.
+};
+
+struct RPCResponseInfo {
+  RPCResponseInfo(std::string const &id_) : id{id_} {} // Convenient
+  RPCResponseInfo() = default;
+
+  struct Error {
+    std::error_code ec;
+    ts::Errata data;
+  };
+
+  std::string id; //!< incoming request id (only used for method calls, empty means it's a notification as requests with empty id
+                  //!< will not pass the validation)
+  Error error;    //!< Error code and details.
+  RPCHandlerResponse callResult; //!< the actual handler's response
+};
+
+///
+/// @brief Class that contains all the request information.
+/// This class maps the jsonrpc protocol for a request. It can be used for Methods and Notifications.
+/// Notifications will not use the id, this is the main reason why is a std::optional<>.
+///
+struct RPCRequestInfo {
+  RPCRequestInfo() = default;
+  RPCRequestInfo(std::string const &version, std::string const &mid) : jsonrpc(version), id(mid) {}
+  std::string jsonrpc; //!<  JsonRPC version ( we only allow 2.0 ). @see yamlcpp_json_decoder
+  std::string method;  //!< incoming method name.
+  std::string id;    //!< incoming request id (only used for method calls, empty means it's a notification as requests with empty id
+                     //!< will not pass the validation)
+  YAML::Node params; //!< incoming parameter structure.
+
+  /// Convenience functions that checks for the type of request. If contains id then it should be handle as method call, otherwise
+  /// will be a notification.
+  bool
+  is_notification() const
+  {
+    return id.empty();
+  }
+  bool
+  is_method() const
+  {
+    return !this->is_notification();
+  }
+};
+
+template <class M> class RPCMessage;
+using RPCRequest  = RPCMessage<std::pair<RPCRequestInfo, std::error_code>>;
+using RPCResponse = RPCMessage<RPCResponseInfo>;
+
+///
+/// @brief Class that reprecent a RPC message, it could be either the request or the response.
+/// Requests @see RPCRequest are represented by a vector of pairs, which contains, the request @see RPCRequestInfo and an associated
+/// error_code in case that the request fails. The main reason of this to be a pair is that as per the protocol specs we need to
+/// respond every message(if it's a method), so for cases like a batch request where you may have some of the request fail, then the
+/// error will be attached to the response. The order is not important
+///
+/// Responses @see RPCResponse models pretty much the same structure except that there is no error associated with it. The @see
+/// RPCResponseInfo could be the error.
+///
+/// @tparam Message The type of the RPCMessage, either a pair of @see RPCRequestInfo, std::error_code. Or @see RPCResponseInfo
+///
+template <typename Message> class RPCMessage
+{
+  static_assert(!std::is_same_v<Message, RPCRequest> || !std::is_same_v<Message, RPCResponse>,
+                "Ups, only RPCRequest or RPCResponse");
+
+  using MessageList = std::vector<Message>;
+  /// @brief to keep track of internal message data.
+  struct Metadata {
+    Metadata() = default;
+    Metadata(bool batch) : isBatch(batch) {}
+    /// @brief Used to mark the incoming request and response base on the former's format. If we want to respond with the same
+    /// format as the incoming request, then this should be used. base on the request's format.
+    enum class MsgFormat {
+      UNKNOWN = 0, //!< Default value.
+      JSON,        //!< If messages arrives as JSON
+      YAML         //!< If messages arrives as YAML
+    };
+    MsgFormat msgFormat{MsgFormat::UNKNOWN};
+    bool isBatch{false};
+  };
+
+public:
+  RPCMessage() {}
+  RPCMessage(bool isBatch) : _metadata{isBatch} {}
+  ///
+  /// @brief
+  ///
+  /// @tparam Msg
+  /// @param msg
+  ///
+  template <typename Msg>
+  void
+  add_message(Msg &&msg)
+  {
+    _elements.push_back(std::forward<Msg>(msg));
+  }
+
+  const MessageList &
+  get_messages() const
+  {
+    return _elements;
+  }
+
+  bool
+  is_notification() const noexcept
+  {
+    return _elements.size() == 0;
+  }
+
+  bool
+  is_batch() const noexcept
+  {
+    return _metadata.isBatch;
+  }
+
+  void
+  is_batch(bool isBatch) noexcept
+  {
+    _metadata.isBatch = isBatch;
+  }
+  void
+  reserve(std::size_t size)
+  {
+    _elements.reserve(size);
+  }
+
+  bool
+  is_json_format() const noexcept
+  {
+    return _metadata.msgFormat == Metadata::MsgFormat::JSON;
+  }
+
+  bool
+  is_yaml_format() const noexcept
+  {
+    return _metadata.msgFormat == Metadata::MsgFormat::YAML;
+  }
+
+private:
+  MessageList _elements;
+  Metadata _metadata;
+};
+
+} // namespace rpc::specs
diff --git a/mgmt/rpc/jsonrpc/JsonRPC.h b/mgmt/rpc/jsonrpc/JsonRPC.h
new file mode 100644
index 0000000..c382723
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/JsonRPC.h
@@ -0,0 +1,58 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include "JsonRPCManager.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+
+namespace rpc
+{
+/// Generic and global JSONRPC service provider info object. It's recommended to use this object when registering your new handler
+/// into the rpc system IF the implementor wants the handler to be listed as ATS's handler.
+extern RPCRegistryInfo core_ats_rpc_service_provider_handle;
+// -----------------------------------------------------------------------------
+/// Set of convenience API function. Users can avoid having to name the whole rps::JsonRPCManager::instance() to use the object.
+
+/// @see JsonRPCManager::add_method_handler for details
+template <typename Func>
+inline bool
+add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt)
+{
+  return JsonRPCManager::instance().add_method_handler(name, std::forward<Func>(call), info, opt);
+}
+
+/// As a plugin you should use  @c TSRPCRegisterMethodHandlerfor to register your function to handle RPC messages.
+template <typename Func>
+inline bool
+add_method_handler_from_plugin(const std::string &name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt)
+{
+  return JsonRPCManager::instance().add_method_handler_from_plugin(name, std::forward<Func>(call), info, opt);
+}
+
+/// @see JsonRPCManager::add_notification_handler for details
+/// @note Plugins must use @c TSRPCRegisterNotificationHandler to register.
+template <typename Func>
+inline bool
+add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt)
+{
+  return JsonRPCManager::instance().add_notification_handler(name, std::forward<Func>(call), info, opt);
+}
+
+} // namespace rpc
diff --git a/mgmt/rpc/jsonrpc/JsonRPCManager.cc b/mgmt/rpc/jsonrpc/JsonRPCManager.cc
new file mode 100644
index 0000000..050f5a4
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/JsonRPCManager.cc
@@ -0,0 +1,356 @@
+/**
+  @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 "JsonRPCManager.h"
+
+#include <iostream>
+#include <chrono>
+#include <system_error>
+#include <mutex>
+#include <condition_variable>
+
+#include "json/YAMLCodec.h"
+
+namespace
+{
+// RPC service info constants.
+const std::string RPC_SERVICE_METHOD_STR{"method"};
+const std::string RPC_SERVICE_NOTIFICATION_STR{"notification"};
+const std::string RPC_SERVICE_NAME_KEY{"name"};
+const std::string RPC_SERVICE_TYPE_KEY{"type"};
+const std::string RPC_SERVICE_PROVIDER_KEY{"provider"};
+const std::string RPC_SERVICE_SCHEMA_KEY{"schema"};
+const std::string RPC_SERVICE_METHODS_KEY{"methods"};
+const std::string RPC_SERVICE_NOTIFICATIONS_KEY{"notifications"};
+const std::string RPC_SERVICE_PRIVILEGED_KEY{"privileged"};
+const std::string RPC_SERVICE_N_A_STR{"N/A"};
+
+// jsonrpc log tag.
+constexpr auto logTag    = "rpc";
+constexpr auto logTagMsg = "rpc.msg";
+} // namespace
+
+namespace rpc
+{
+RPCRegistryInfo core_ats_rpc_service_provider_handle = {
+  "Traffic Server JSONRPC 2.0 API" // Provider's description
+};
+
+// plugin rpc handling variables.
+std::mutex g_rpcHandlingMutex;
+std::condition_variable g_rpcHandlingCompletion;
+ts::Rv<YAML::Node> g_rpcHandlerResponseData;
+bool g_rpcHandlerProcessingCompleted{false};
+
+// --- Helpers
+std::pair<ts::Errata, error::RPCErrorCode>
+check_for_blockers(Context const &ctx, TSRPCHandlerOptions const &options)
+{
+  if (auto err = ctx.get_auth().is_blocked(options); !err.isOK()) {
+    return {err, error::RPCErrorCode::Unauthorized};
+  }
+  return {};
+}
+
+// --- Dispatcher
+JsonRPCManager::Dispatcher::Dispatcher()
+{
+  register_service_descriptor_handler();
+}
+void
+JsonRPCManager::Dispatcher::register_service_descriptor_handler()
+{
+  if (!this->add_handler<Dispatcher::Method, MethodHandlerSignature>(
+        "show_registered_handlers",
+        [this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> {
+          return show_registered_handlers(id, req);
+        },
+        &core_ats_rpc_service_provider_handle, {{NON_RESTRICTED_API}})) {
+    Warning("Handler already registered.");
+  }
+
+  if (!this->add_handler<Dispatcher::Method, MethodHandlerSignature>(
+        "get_service_descriptor",
+        [this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> { return get_service_descriptor(id, req); },
+        &core_ats_rpc_service_provider_handle, {{NON_RESTRICTED_API}})) {
+    Warning("Handler already registered.");
+  }
+}
+
+JsonRPCManager::Dispatcher::response_type
+JsonRPCManager::Dispatcher::dispatch(Context const &ctx, specs::RPCRequestInfo const &request) const
+{
+  std::error_code ec;
+  auto const &handler = find_handler(request, ec);
+
+  if (ec) {
+    specs::RPCResponseInfo resp{request.id};
+    resp.error.ec = ec;
+    return resp;
+  }
+
+  // We have got a valid handler, we will now check if the context holds any restriction for this handler to be called.
+  if (auto &&[errata, ec] = check_for_blockers(ctx, handler.get_options()); !errata.isOK()) {
+    specs::RPCResponseInfo resp{request.id};
+    resp.error.ec   = ec;
+    resp.error.data = errata;
+    return resp;
+  }
+
+  if (request.is_notification()) {
+    return invoke_notification_handler(handler, request);
+  }
+
+  // just a method call.
+  return invoke_method_handler(handler, request);
+}
+
+JsonRPCManager::Dispatcher::InternalHandler const &
+JsonRPCManager::Dispatcher::find_handler(specs::RPCRequestInfo const &request, std::error_code &ec) const
+{
+  static InternalHandler no_handler{};
+
+  std::lock_guard<std::mutex> lock(_mutex);
+
+  auto search = _handlers.find(request.method);
+
+  if (search == std::end(_handlers)) {
+    // no more checks, no handler either notification or method
+    ec = error::RPCErrorCode::METHOD_NOT_FOUND;
+    return no_handler;
+  }
+
+  // Handler's method type should match the requested method type.
+  if ((request.is_method() && search->second.is_method()) || (request.is_notification() && !search->second.is_method())) {
+    return search->second;
+  }
+
+  ec = error::RPCErrorCode::INVALID_REQUEST;
+  return no_handler;
+}
+
+JsonRPCManager::Dispatcher::response_type
+JsonRPCManager::Dispatcher::invoke_method_handler(JsonRPCManager::Dispatcher::InternalHandler const &handler,
+                                                  specs::RPCRequestInfo const &request) const
+{
+  specs::RPCResponseInfo response{request.id};
+
+  try {
+    auto const &rv = handler.invoke(request);
+
+    if (rv.isOK()) {
+      response.callResult.result = rv.result();
+    } else {
+      // if we have some errors to log, then include it.
+      response.callResult.errata = rv.errata();
+    }
+  } catch (std::exception const &e) {
+    Debug(logTag, "Oops, something happened during the callback invocation: %s", e.what());
+    response.error.ec = error::RPCErrorCode::ExecutionError;
+  }
+
+  return response;
+}
+
+JsonRPCManager::Dispatcher::response_type
+JsonRPCManager::Dispatcher::invoke_notification_handler(JsonRPCManager::Dispatcher::InternalHandler const &handler,
+                                                        specs::RPCRequestInfo const &notification) const
+{
+  try {
+    handler.invoke(notification);
+  } catch (std::exception const &e) {
+    Debug(logTag, "Oops, something happened during the callback(notification) invocation: %s", e.what());
+    // it's a notification so we do not care much.
+  }
+
+  return {std::nullopt};
+}
+
+bool
+JsonRPCManager::Dispatcher::remove_handler(std::string_view name)
+{
+  std::lock_guard<std::mutex> lock(_mutex);
+  auto foundIt = std::find_if(std::begin(_handlers), std::end(_handlers), [&](auto const &p) { return p.first == name; });
+  if (foundIt != std::end(_handlers)) {
+    _handlers.erase(foundIt);
+    return true;
+  }
+
+  return false;
+}
+// --- JsonRPCManager
+bool
+JsonRPCManager::remove_handler(std::string_view name)
+{
+  return _dispatcher.remove_handler(name);
+}
+
+std::optional<std::string>
+JsonRPCManager::handle_call(Context const &ctx, std::string const &request)
+{
+  Debug(logTagMsg, "--> JSONRPC request\n'%s'", request.c_str());
+
+  std::error_code ec;
+  try {
+    // let's decode all the incoming messages into our own types.
+    specs::RPCRequest const &msg = Decoder::decode(request, ec);
+
+    // If any error happened within the request, they will be kept inside each
+    // particular request, as they would need to be converted back in a proper error response.
+    if (ec) {
+      specs::RPCResponseInfo resp;
+      resp.error.ec = ec;
+      return Encoder::encode(resp);
+    }
+
+    specs::RPCResponse response{msg.is_batch()};
+    for (auto const &[req, decode_error] : msg.get_messages()) {
+      // As per jsonrpc specs we do care about invalid messages as long as they are well-formed,  our decode logic will make their
+      // best to build up a request, if any errors were detected during decoding, we will save the error and make it part of the
+      // RPCRequest elements for further use.
+      if (!decode_error) {
+        // request seems ok and ready to be dispatched. The dispatcher will tell us if the method exist and if so, it will dispatch
+        // the call and gives us back the response.
+        auto encodedResponse = _dispatcher.dispatch(ctx, req);
+
+        if (encodedResponse) {
+          // if any error was detected during invocation or before, the response will have the error field set, so this will
+          // internally be converted to the right response type.
+          response.add_message(std::move(*encodedResponse));
+        } // else it's a notification and no error.
+
+      } else {
+        // If the request was marked as an error(decode error), we still need to send the error back, so we save it.
+        specs::RPCResponseInfo resp{req.id};
+        resp.error.ec = decode_error;
+        response.add_message(std::move(resp));
+      }
+    }
+
+    // We will not have a response for notification(s); This could be a batch of notifications only.
+    std::optional<std::string> resp;
+
+    if (!response.is_notification()) {
+      resp = Encoder::encode(response);
+      Debug(logTagMsg, "<-- JSONRPC Response\n '%s'", (*resp).c_str());
+    }
+    return resp;
+  } catch (std::exception const &ex) {
+    ec = error::RPCErrorCode::INTERNAL_ERROR;
+  }
+  specs::RPCResponseInfo resp;
+  resp.error.ec = ec;
+  return {Encoder::encode(resp)};
+}
+
+// ---------------------------- InternalHandler ---------------------------------
+inline ts::Rv<YAML::Node>
+JsonRPCManager::Dispatcher::InternalHandler::invoke(specs::RPCRequestInfo const &request) const
+{
+  ts::Rv<YAML::Node> ret;
+  std::visit(ts::meta::overloaded{[](std::monostate) -> void { /* no op */ },
+                                  [&request](Notification const &handler) -> void {
+                                    // Notification handler call. Ignore response, there is no completion cv check in here basically
+                                    // because we do  not deal with any response, the callee can just re-schedule the work if
+                                    // needed. We fire and forget.
+                                    handler.cb(request.params);
+                                  },
+                                  [&ret, &request](Method const &handler) -> void {
+                                    // Regular Method Handler call, No cond variable check here, this should have not be created by
+                                    // a plugin.
+                                    ret = handler.cb(request.id, request.params);
+                                  },
+                                  [&ret, &request](PluginMethod const &handler) -> void {
+                                    // We call the method handler, we'll lock and wait till the condition_variable
+                                    // gets set on the other side. The handler may return immediately with no response being set.
+                                    // cond var will give us green to proceed.
+                                    handler.cb(request.id, request.params);
+                                    std::unique_lock<std::mutex> lock(g_rpcHandlingMutex);
+                                    g_rpcHandlingCompletion.wait(lock, []() { return g_rpcHandlerProcessingCompleted; });
+                                    g_rpcHandlerProcessingCompleted = false;
+                                    // seems to be done, set the response. As the response data is a ts::Rv this will handle both,
+                                    // error and non error cases.
+                                    ret = g_rpcHandlerResponseData;
+                                    // clean up the shared data.
+                                    g_rpcHandlerResponseData.clear();
+                                    lock.unlock();
+                                  }},
+             this->_func);
+  return ret;
+}
+
+inline bool
+JsonRPCManager::Dispatcher::InternalHandler::is_method() const
+{
+  const auto index = static_cast<InternalHandler::VariantTypeIndexId>(_func.index());
+  switch (index) {
+  case VariantTypeIndexId::METHOD:
+  case VariantTypeIndexId::METHOD_FROM_PLUGIN:
+    return true;
+    break;
+  default:;
+  }
+  // For now, we say, if not method, it's a notification.
+  return false;
+}
+
+ts::Rv<YAML::Node>
+JsonRPCManager::Dispatcher::show_registered_handlers(std::string_view const &, const YAML::Node &)
+{
+  ts::Rv<YAML::Node> resp;
+  std::lock_guard<std::mutex> lock(_mutex);
+  for (auto const &[name, handler] : _handlers) {
+    std::string const &key = handler.is_method() ? RPC_SERVICE_METHODS_KEY : RPC_SERVICE_NOTIFICATIONS_KEY;
+    resp.result()[key].push_back(name);
+  }
+  return resp;
+}
+
+// -----------------------------------------------------------------------------
+// This jsonrpc handler can provides a service descriptor for the RPC
+ts::Rv<YAML::Node>
+JsonRPCManager::Dispatcher::get_service_descriptor(std::string_view const &, const YAML::Node &)
+{
+  YAML::Node rpcService;
+  std::lock_guard<std::mutex> lock(_mutex);
+  // std::for_each(std::begin(_handlers), std::end(_handlers), [&rpcService](auto const &h) {
+  for (auto const &[name, handler] : _handlers) {
+    YAML::Node method;
+    method[RPC_SERVICE_NAME_KEY] = name;
+    method[RPC_SERVICE_TYPE_KEY] = handler.is_method() ? RPC_SERVICE_METHOD_STR : RPC_SERVICE_NOTIFICATION_STR;
+    /* most of this information will be eventually populated from the RPCRegistryInfo object */
+    auto regInfo = handler.get_reg_info();
+    std::string provider;
+    if (regInfo && regInfo->provider.size()) {
+      provider = std::string{regInfo->provider};
+    } else {
+      provider = RPC_SERVICE_N_A_STR;
+    }
+    method[RPC_SERVICE_PROVIDER_KEY]   = provider;
+    method[RPC_SERVICE_PRIVILEGED_KEY] = handler.get_options().auth.restricted;
+    YAML::Node schema{YAML::NodeType::Map}; // no schema for now, but we have a placeholder for it. Schema should provide
+                                            // description and all the details about the call
+    method[RPC_SERVICE_SCHEMA_KEY] = std::move(schema);
+    rpcService[RPC_SERVICE_METHODS_KEY].push_back(method);
+  }
+
+  return rpcService;
+}
+} // namespace rpc
diff --git a/mgmt/rpc/jsonrpc/JsonRPCManager.h b/mgmt/rpc/jsonrpc/JsonRPCManager.h
new file mode 100644
index 0000000..4baeda0
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/JsonRPCManager.h
@@ -0,0 +1,340 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <iostream>
+#include <map>
+#include <functional>
+#include <tuple>
+#include <forward_list>
+#include <string_view>
+#include <variant>
+#include <mutex>
+
+#include "tscore/Errata.h"
+#include "tscore/Diags.h"
+#include "tscpp/util/ts_meta.h"
+#include "ts/apidefs.h"
+
+#include "Defs.h"
+#include "Context.h"
+
+namespace rpc
+{
+// forward
+namespace json_codecs
+{
+  class yamlcpp_json_decoder;
+  class yamlcpp_json_encoder;
+} // namespace json_codecs
+
+///
+/// @brief This class keeps all relevant @c RPC provider's info.
+///
+struct RPCRegistryInfo {
+  std::string_view provider; ///< Who's the rpc endpoint provider, could be ATS or a plugins. When requesting the service info from
+                             ///< the rpc node, this will be part of the service info.
+};
+
+///
+/// @brief JSONRPC registration and JSONRPC invocation logic  https://www.jsonrpc.org/specification
+/// doc TBC
+class JsonRPCManager
+{
+private:
+  /// @note In case we want to change the codecs and use another library, we just need to follow the same signatures @see
+  /// yamlcpp_json_decoder and @see yamlcpp_json_encoder.
+  // We use the yamlcpp library by default.
+  using Decoder = json_codecs::yamlcpp_json_decoder;
+  using Encoder = json_codecs::yamlcpp_json_encoder;
+
+public:
+  // Possible RPC method signatures.
+  using MethodHandlerSignature       = std::function<ts::Rv<YAML::Node>(std::string_view const &, const YAML::Node &)>;
+  using PluginMethodHandlerSignature = std::function<void(std::string_view const &, const YAML::Node &)>;
+  using NotificationHandlerSignature = std::function<void(const YAML::Node &)>;
+
+  ///
+  /// @brief Add new registered method handler to the JSON RPC engine.
+  ///
+  /// @tparam Func The callback function type. See @c MethodHandlerSignature
+  /// @param name Name to be exposed by the RPC Engine, this should match the incoming request. i.e: If you register 'get_stats'
+  ///             then the incoming jsonrpc call should have this very same name in the 'method' field. .. {...'method':
+  ///             'get_stats'...} .
+  /// @param call The function handler.
+  /// @param info RPCRegistryInfo pointer.
+  /// @param opt  Handler options, used to pass information about the registered handler.
+  /// @return bool Boolean flag. true if the callback was successfully added, false otherwise
+  ///
+  template <typename Func>
+  bool add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt);
+
+  ///
+  /// @brief Add new registered method handler(from a plugin scope) to the JSON RPC engine.
+  ///
+  /// Function to add a new RPC handler from a plugin context. This will be invoked by @c TSRPCRegisterMethodHandler. If you
+  /// register your handler by using this API then you have to  express the result of the processing by either calling
+  /// @c TSInternalHandlerDone or @c TSInternalHandlerError in case of an error.
+  /// When a function registered by this mechanism gets called, the response from the handler will not matter, instead we will rely
+  /// on what the @c TSInternalHandlerDone or @c TSInternalHandlerError have set.
+  ///
+  /// @note If you are not a plugin, do not call this function. Use @c add_method_handler instead.
+  ///
+  /// @tparam Func The callback function type. See @c PluginMethodHandlerSignature
+  /// @param name Name to be exposed by the RPC Engine, this should match the incoming request. i.e: If you register 'get_stats'
+  ///             then the incoming jsonrpc call should have this very same name in the 'method' field. .. {...'method':
+  ///             'get_stats'...} .
+  /// @param call The function handler.
+  /// @param info RPCRegistryInfo pointer.
+  /// @param opt  Handler options, used to pass information about the registered handler.
+  /// @return bool Boolean flag. true if the callback was successfully added, false otherwise
+  ///
+  template <typename Func>
+  bool add_method_handler_from_plugin(const std::string &name, Func &&call, const RPCRegistryInfo *info,
+                                      TSRPCHandlerOptions const &opt);
+
+  ///
+  /// @brief Add new registered notification handler to the JSON RPC engine.
+  ///
+  /// @tparam Func The callback function type. See @c NotificationHandlerSignature
+  /// @param name Name to be exposed by the RPC Engine.
+  /// @param call The callback function that needs handler.
+  /// @param info RPCRegistryInfo pointer.
+  /// @param opt  Handler options, used to pass information about the registered handler.
+  /// @return bool Boolean flag. true if the callback was successfully added, false otherwise
+  ///
+  template <typename Func>
+  bool add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt);
+
+  ///
+  /// @brief This function handles the incoming jsonrpc request and dispatch the associated registered handler.
+  ///
+  /// @param ctx @c Context object used pass information between rpc layers.
+  /// @param jsonString The incoming jsonrpc 2.0 message. \link https://www.jsonrpc.org/specification
+  /// @return std::optional<std::string> For methods, a valid jsonrpc 2.0 json string will be passed back. Notifications will not
+  ///         contain any json back.
+  ///
+  std::optional<std::string> handle_call(Context const &ctx, std::string const &jsonString);
+
+  ///
+  /// @brief Get the instance of the whole RPC engine.
+  ///
+  /// @return JsonRPCManager& The JsonRPCManager protocol implementation object.
+  ///
+  static JsonRPCManager &
+  instance()
+  {
+    static JsonRPCManager rpc;
+    return rpc;
+  }
+
+protected: // For unit test.
+  JsonRPCManager()                       = default;
+  JsonRPCManager(JsonRPCManager const &) = delete;
+  JsonRPCManager(JsonRPCManager &&)      = delete;
+  JsonRPCManager &operator=(JsonRPCManager const &) = delete;
+  JsonRPCManager &operator=(JsonRPCManager &&) = delete;
+
+  ///
+  /// @brief Remove handler from the registered method handlers. Test only.
+  ///
+  /// @param name Method name.
+  /// @return true If all is good.
+  /// @return false If we could not remove it.
+  ///
+  bool remove_handler(std::string_view name);
+  friend bool test_remove_handler(std::string_view name);
+
+private:
+  ///
+  /// @brief Internal class that holds and handles the dispatch logic.
+  ///
+  /// It holds methods and notifications as well as provides the mechanism to call each particular handler.
+  ///
+  /// Design notes:
+  ///
+  /// This class holds a std::unordered_map<std::string, InternalHandler> as a main table for all the callbacks.
+  /// The @c InternalHandler wraps a std::variant with the supported handler types, depending on each handler type the invocation
+  /// varies. All handlers gets call synchronously with the difference that for Plugin handlers (see @c PluginMethod) we will wait
+  /// for the response to be set, plugins are provided with an API to deal with different responses(success or error), plugins do
+  /// not require to respond to the callback with a response, see @c PluginMethodHandlerSignature .
+  /// @c FunctionWrapper class holds the actual @c std::function<T> object, this class is needed to make easy to handle equal
+  /// signatures inside a @c std::variant
+  class Dispatcher
+  {
+    /// The response type used internally, notifications won't fill in the optional response.  Internal response's @ ec will be set
+    /// in case of any error.
+    using response_type = std::optional<specs::RPCResponseInfo>;
+
+    ///
+    /// @brief Class that wraps the actual std::function<T>.
+    ///
+    /// @tparam T Handler signature See @c MethodHandlerSignature @c PluginMethodHandlerSignature @c NotificationHandlerSignature
+    ///
+    template <typename T> struct FunctionWrapper {
+      FunctionWrapper(T &&t) : cb(std::forward<T>(t)) {}
+
+      T cb; ///< Function handler (std::function<T>)
+    };
+
+    struct InternalHandler; ///< fw declaration
+
+  public:
+    Dispatcher();
+    /// Add a method handler to the internal container
+    /// @return True if was successfully added, False otherwise.
+    template <typename FunctionWrapperType, typename Handler>
+    bool add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt);
+
+    /// Find and call the request's callback. If any error occurs, the return type will have the specific error.
+    /// For notifications the @c RPCResponseInfo will not be set as part of the response. @c response_type
+    response_type dispatch(Context const &ctx, specs::RPCRequestInfo const &request) const;
+
+    /// Find a particular registered handler(method) by its associated name.
+    /// @return A pair. The handler itself and a boolean flag indicating that the handler was found. If not found, second will
+    ///         be false and the handler null.
+    InternalHandler const &find_handler(specs::RPCRequestInfo const &request, std::error_code &ec) const;
+    /// Removes a method handler. Unit test mainly.
+    bool remove_handler(std::string_view name);
+
+    // JSONRPC API - here for now.
+    ts::Rv<YAML::Node> show_registered_handlers(std::string_view const &, const YAML::Node &);
+    ts::Rv<YAML::Node> get_service_descriptor(std::string_view const &, const YAML::Node &);
+
+    // Supported handler endpoint types.
+    using Method       = FunctionWrapper<MethodHandlerSignature>;
+    using PluginMethod = FunctionWrapper<PluginMethodHandlerSignature>;
+    // Plugins and non plugins handlers have no difference from the point of view of the RPC manager, we call and we do not expect
+    // for the work to be finished. Notifications have no response at all.
+    using Notification = FunctionWrapper<NotificationHandlerSignature>;
+
+  private:
+    /// Register our own api into the RPC manager. This will expose the methods and notifications registered in the RPC
+    void register_service_descriptor_handler();
+
+    // Functions to deal with the handler invocation.
+    response_type invoke_method_handler(InternalHandler const &handler, specs::RPCRequestInfo const &request) const;
+    response_type invoke_notification_handler(InternalHandler const &handler, specs::RPCRequestInfo const &request) const;
+
+    ///
+    /// @brief Class that wraps callable objects of any RPC specific type. If provided, this class also holds a valid registry
+    /// information
+    ///
+    /// This class holds the actual callable object from one of our supported types, this helps us to
+    /// simplify the logic to insert and fetch callable objects from our container.
+    struct InternalHandler {
+      InternalHandler() = default;
+      InternalHandler(const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt) : _regInfo(info), _options(opt) {}
+      /// Sets the handler.
+      template <class T, class F> void set_callback(F &&t);
+      explicit operator bool() const;
+      bool operator!() const;
+      /// Invoke the actual handler callback.
+      ts::Rv<YAML::Node> invoke(specs::RPCRequestInfo const &request) const;
+      /// Check if the handler was registered as method.
+      bool is_method() const;
+
+      /// Returns the internal registry info.
+      const RPCRegistryInfo *
+      get_reg_info() const
+      {
+        return _regInfo;
+      }
+
+      /// Returns the configured options associated with this particular handler.
+      TSRPCHandlerOptions const &
+      get_options() const
+      {
+        return _options;
+      }
+
+    private:
+      // We need to keep this match with the order of types in the _func variant. This will help us to identify the holding type.
+      enum class VariantTypeIndexId : std::size_t { NOTIFICATION = 1, METHOD = 2, METHOD_FROM_PLUGIN = 3 };
+      // We support these three for now. This can easily be extended to support other signatures.
+      // that's one of the main points of the InternalHandler
+      std::variant<std::monostate, Notification, Method, PluginMethod> _func;
+      const RPCRegistryInfo *_regInfo; ///< Can hold internal information about the handler, this could be null as it is optional.
+                                       ///< This pointer can eventually holds important information about the call.
+      TSRPCHandlerOptions _options;
+    };
+    // We will keep all the handlers wrapped inside the InternalHandler class, this will help us
+    // to have a single container for all the types(method, notification & plugin method(cond var)).
+    std::unordered_map<std::string, InternalHandler> _handlers; ///< Registered handler container.
+    mutable std::mutex _mutex;                                  ///< insert/find/delete mutex.
+  };
+
+  Dispatcher _dispatcher; ///< Internal handler container and dispatcher logic object.
+};
+
+// ------------------------------ JsonRPCManager -------------------------------
+template <typename Handler>
+bool
+JsonRPCManager::add_method_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info,
+                                   TSRPCHandlerOptions const &opt)
+{
+  return _dispatcher.add_handler<Dispatcher::Method, Handler>(name, std::forward<Handler>(call), info, opt);
+}
+template <typename Handler>
+bool
+JsonRPCManager::add_method_handler_from_plugin(const std::string &name, Handler &&call, const RPCRegistryInfo *info,
+                                               TSRPCHandlerOptions const &opt)
+{
+  return _dispatcher.add_handler<Dispatcher::PluginMethod, Handler>(name, std::forward<Handler>(call), info, opt);
+}
+
+template <typename Handler>
+bool
+JsonRPCManager::add_notification_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info,
+                                         TSRPCHandlerOptions const &opt)
+{
+  return _dispatcher.add_handler<Dispatcher::Notification, Handler>(name, std::forward<Handler>(call), info, opt);
+}
+
+// ----------------------------- InternalHandler -------------------------------
+template <class T, class F>
+void
+JsonRPCManager::Dispatcher::InternalHandler::set_callback(F &&f)
+{
+  // T would be one of the handler endpoint types.
+  _func = T{std::forward<F>(f)};
+}
+inline JsonRPCManager::Dispatcher::InternalHandler::operator bool() const
+{
+  return _func.index() != 0;
+}
+bool inline JsonRPCManager::Dispatcher::InternalHandler::operator!() const
+{
+  return _func.index() == 0;
+}
+
+// ----------------------------- Dispatcher ------------------------------------
+template <typename FunctionWrapperType, typename Handler>
+bool
+JsonRPCManager::Dispatcher::add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info,
+                                        TSRPCHandlerOptions const &opt)
+{
+  std::lock_guard<std::mutex> lock(_mutex);
+  InternalHandler call{info, opt};
+  call.set_callback<FunctionWrapperType>(std::forward<Handler>(handler));
+  return _handlers.emplace(name, std::move(call)).second;
+}
+
+} // namespace rpc
diff --git a/mgmt/rpc/jsonrpc/error/RPCError.cc b/mgmt/rpc/jsonrpc/error/RPCError.cc
new file mode 100644
index 0000000..be02e56
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/error/RPCError.cc
@@ -0,0 +1,93 @@
+/**
+  @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 "RPCError.h"
+
+#include <string>
+#include <system_error>
+
+namespace
+{ // anonymous namespace
+
+struct RPCErrorCategory : std::error_category {
+  const char *name() const noexcept override;
+  std::string message(int ev) const override;
+};
+
+const char *
+RPCErrorCategory::name() const noexcept
+{
+  return "rpc_msg";
+}
+
+std::string
+RPCErrorCategory::message(int ev) const
+{
+  using namespace rpc::error;
+  switch (static_cast<RPCErrorCode>(ev)) {
+  case RPCErrorCode::INVALID_REQUEST:
+    return {"Invalid Request"};
+  case RPCErrorCode::METHOD_NOT_FOUND:
+    return {"Method not found"};
+  case RPCErrorCode::INVALID_PARAMS:
+    return {"Invalid params"};
+  case RPCErrorCode::INTERNAL_ERROR:
+    return {"Internal error"};
+  case RPCErrorCode::PARSE_ERROR:
+    return {"Parse error"};
+  case RPCErrorCode::InvalidVersion:
+    return {"Invalid version, 2.0 only"};
+  case RPCErrorCode::InvalidVersionType:
+    return {"Invalid version type, should be a string"};
+  case RPCErrorCode::MissingVersion:
+    return {"Missing version field"};
+  case RPCErrorCode::InvalidMethodType:
+    return {"Invalid method type, should be a string"};
+  case RPCErrorCode::MissingMethod:
+    return {"Missing method field"};
+  case RPCErrorCode::InvalidParamType:
+    return {"Invalid params type. A Structured value is expected"};
+  case RPCErrorCode::InvalidIdType:
+    return {"Invalid id type"};
+  case RPCErrorCode::NullId:
+    return {"Use of null as id is discouraged"};
+  case RPCErrorCode::ExecutionError:
+    return {"Error during execution"};
+  case RPCErrorCode::Unauthorized:
+    return {"Unauthorized action"};
+  case RPCErrorCode::EmptyId:
+    return {"Use of an empty string as id is discouraged"};
+  default:
+    return "Rpc error " + std::to_string(ev);
+  }
+}
+
+const RPCErrorCategory rpcErrorCategory{};
+} // anonymous namespace
+
+namespace rpc::error
+{
+std::error_code
+make_error_code(rpc::error::RPCErrorCode e)
+{
+  return {static_cast<int>(e), rpcErrorCategory};
+}
+
+} // namespace rpc::error
diff --git a/mgmt/rpc/jsonrpc/error/RPCError.h b/mgmt/rpc/jsonrpc/error/RPCError.h
new file mode 100644
index 0000000..44f81f8
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/error/RPCError.h
@@ -0,0 +1,64 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <string>
+#include <map>
+#include <unordered_map>
+#include <cstdint>
+#include <cassert>
+#include <optional>
+#include <system_error>
+
+namespace rpc::error
+{
+enum class RPCErrorCode {
+  // for std::error_code to work, we shouldn't define 0.
+
+  // JSONRPC 2.0 protocol defined errors.
+  INVALID_REQUEST  = -32600,
+  METHOD_NOT_FOUND = -32601,
+  INVALID_PARAMS   = -32602,
+  INTERNAL_ERROR   = -32603,
+  PARSE_ERROR      = -32700,
+
+  // Custom errors. A more grained error codes than the main above.
+  InvalidVersion = 1, //!< Version should be equal to "2.0".
+  InvalidVersionType, //!< Invalid string conversion.
+  MissingVersion,     //!< Missing version field.
+  InvalidMethodType,  //!< Should be a string.
+  MissingMethod,      //!< Method name missing.
+  InvalidParamType,   //!< Not a valid structured type.
+  InvalidIdType,      //!< Invalid string conversion.
+  NullId,             //!< null id.
+  ExecutionError,     //!< Handler's general error.
+  Unauthorized,       //!< In case we want to block the call based on privileges, access permissions, etc.
+  EmptyId             //!< Empty id("").
+};
+// TODO: force non 0 check
+std::error_code make_error_code(rpc::error::RPCErrorCode e);
+
+} // namespace rpc::error
+
+namespace std
+{
+template <> struct is_error_code_enum<rpc::error::RPCErrorCode> : true_type {
+};
+} // namespace std
diff --git a/mgmt/rpc/jsonrpc/json/YAMLCodec.h b/mgmt/rpc/jsonrpc/json/YAMLCodec.h
new file mode 100644
index 0000000..1d7668d
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/json/YAMLCodec.h
@@ -0,0 +1,304 @@
+/**
+  @file YAMLCodec.h
+  @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.
+*/
+#pragma once
+
+#include <yaml-cpp/yaml.h>
+#include "rpc/jsonrpc/error/RPCError.h"
+#include "rpc/jsonrpc/Defs.h"
+
+namespace rpc::json_codecs
+{
+///
+/// @note The overall design is to make this classes @c yamlcpp_json_decoder and @c yamlcpp_json_encoder plugables into the Json Rpc
+/// encode/decode logic. yamlcpp does not give us all the behavior we need, such as the way it handles the null values. Json needs
+/// to use literal null and yamlcpp uses ~. If this becomes a problem, then we may need to change the codec implementation, we just
+/// follow the api and it should work with minimum changes.
+///
+
+///
+/// @brief Implements json to request object decoder.
+/// This class converts and validate the incoming string into a request object.
+///
+class yamlcpp_json_decoder
+{
+  ///
+  /// @brief Function that decodes and validate the fields base on the JSONRPC 2.0 protocol, All this is based on the specs for each
+  /// field.
+  ///
+  /// @param node  @see YAML::Node that contains all the fields.
+  /// @return std::pair<specs::RPCRequestInfo, std::error_code> It returns a pair of request and an the error reporting.
+  ///
+  static std::pair<specs::RPCRequestInfo, std::error_code>
+  decode_and_validate(YAML::Node const &node) noexcept
+  {
+    specs::RPCRequestInfo request;
+    if (!node.IsDefined() || (node.Type() != YAML::NodeType::Map) || (node.size() == 0)) {
+      // We only care about structures with elements.
+      return {request, error::RPCErrorCode::INVALID_REQUEST};
+    }
+
+    try {
+      // try the id first so we can use for a possible error.
+      // All this may be obsolete if we decided to accept only strings.
+      if (auto id = node["id"]) {
+        if (id.IsNull()) {
+          // if it's present, it should be valid.
+          return {request, error::RPCErrorCode::NullId};
+        }
+
+        try {
+          request.id = id.as<std::string>();
+          if (request.id.empty()) {
+            return {request, error::RPCErrorCode::EmptyId};
+          }
+        } catch (YAML::Exception const &) {
+          return {request, error::RPCErrorCode::InvalidIdType};
+        }
+      } // else ->  it's fine, could be a notification.
+      // version
+      if (auto version = node["jsonrpc"]) {
+        try {
+          request.jsonrpc = version.as<std::string>();
+
+          if (request.jsonrpc != specs::JSONRPC_VERSION) {
+            return {request, error::RPCErrorCode::InvalidVersion};
+          }
+        } catch (YAML::Exception const &ex) {
+          return {request, error::RPCErrorCode::InvalidVersionType};
+        }
+      } else {
+        return {request, error::RPCErrorCode::MissingVersion};
+      }
+      // method
+      if (auto method = node["method"]) {
+        try {
+          request.method = method.as<std::string>();
+        } catch (YAML::Exception const &ex) {
+          return {request, error::RPCErrorCode::InvalidMethodType};
+        }
+      } else {
+        return {request, error::RPCErrorCode::MissingMethod};
+      }
+      // params
+      if (auto params = node["params"]) {
+        // TODO: check schema.
+        switch (params.Type()) {
+        case YAML::NodeType::Map:
+        case YAML::NodeType::Sequence:
+          break;
+        default:
+          return {request, error::RPCErrorCode::InvalidParamType};
+        }
+        request.params = std ::move(params);
+      }
+      // else -> params can be omitted
+    } catch (std::exception const &e) {
+      // we want to keep the request as we will respond with a message.
+      return {request, error::RPCErrorCode::PARSE_ERROR};
+    }
+    // TODO  We may want to extend the error handling and inform the user if there is  more than one invalid field in the request,
+    // so far we notify only the first one, we can use the data field to add more errors in it. ts::Errata
+    return {request, {/*ok*/}};
+  }
+
+public:
+  ///
+  /// @brief Decode a string, either json or yaml into a @see specs::RPCRequest . @c ec will report the error if occurs @see
+  /// RPCErrorCode
+  ///
+  /// @param request The string request, this should be either json or yaml.
+  /// @param ec Output value, The error reporting.
+  /// @return specs::RPCRequest A valid rpc response object if no errors.
+  ///
+  static specs::RPCRequest
+  decode(std::string const &request, std::error_code &ec) noexcept
+  {
+    specs::RPCRequest msg;
+    try {
+      YAML::Node node = YAML::Load(request);
+      switch (node.Type()) {
+      case YAML::NodeType::Map: { // 4
+        // single element
+        msg.add_message(decode_and_validate(node));
+      } break;
+      case YAML::NodeType::Sequence: { // 3
+        // In case we get [] which is valid sequence but invalid jsonrpc message.
+        if (node.size() > 0) {
+          // it's a batch
+          msg.is_batch(true);
+          msg.reserve(node.size());
+
+          for (auto &&n : node) {
+            msg.add_message(decode_and_validate(n));
+          }
+        } else {
+          // Valid json but invalid base on jsonrpc specs, ie: [].
+          ec = error::RPCErrorCode::INVALID_REQUEST;
+        }
+      } break;
+      default:
+        // Only Sequences or Objects are valid.
+        ec = error::RPCErrorCode::INVALID_REQUEST;
+        break;
+      }
+    } catch (YAML::Exception const &e) {
+      ec = error::RPCErrorCode::PARSE_ERROR;
+    }
+    return msg;
+  }
+};
+
+///
+/// @brief Implements request to string encoder.
+/// This class converts a request(including errors) into a json string.
+///
+class yamlcpp_json_encoder
+{
+  ///
+  /// @brief Function to encode an error.
+  /// Error could be from two sources, presence of @c std::error_code means a high level and the @c ts::Errata a callee . Both will
+  /// be written into the passed @c out YAML::Emitter. This is mainly a convenience class for the other two encode_* functions.
+  ///
+  /// @param error std::error_code High level, main error.
+  /// @param errata  the Errata from the callee
+  /// @param json   output parameter. YAML::Emitter.
+  ///
+  static void
+  encode_error(std::error_code error, ts::Errata const &errata, YAML::Emitter &json)
+  {
+    json << YAML::Key << "error";
+    json << YAML::BeginMap;
+    json << YAML::Key << "code" << YAML::Value << error.value();
+    json << YAML::Key << "message" << YAML::Value << error.message();
+    if (!errata.isOK()) {
+      json << YAML::Key << "data";
+      json << YAML::BeginSeq;
+      for (auto const &err : errata) {
+        json << YAML::BeginMap;
+        json << YAML::Key << "code" << YAML::Value << err.getCode();
+        json << YAML::Key << "message" << YAML::Value << err.text();
+        json << YAML::EndMap;
+      }
+      json << YAML::EndSeq;
+    }
+    json << YAML::EndMap;
+  }
+
+  /// Convenience functions to call encode_error.
+  static void
+  encode_error(ts::Errata const &errata, YAML::Emitter &json)
+  {
+    encode_error({error::RPCErrorCode::ExecutionError}, errata, json);
+  }
+
+  ///
+  /// @brief Function to encode a single response(no batch) into an emitter.
+  ///
+  /// @param resp Response object
+  /// @param json output yaml emitter
+  ///
+  static void
+  encode(const specs::RPCResponseInfo &resp, YAML::Emitter &json)
+  {
+    json << YAML::BeginMap;
+    json << YAML::Key << "jsonrpc" << YAML::Value << specs::JSONRPC_VERSION;
+
+    // Important! As per specs, errors have preference over the result, we ignore result if error was set.
+
+    if (resp.error.ec) {
+      // internal library detected error: Decoding, etc.
+      encode_error(resp.error.ec, resp.error.data, json);
+    }
+    // Registered handler error: They have set the error on the response from the registered handler. This uses ExecutionError as
+    // top error.
+    else if (!resp.callResult.errata.isOK()) {
+      encode_error(resp.callResult.errata, json);
+    }
+    // A valid response: The registered handler have set the proper result and no error was flagged.
+    else {
+      json << YAML::Key << "result" << YAML::Value;
+
+      // Could be the case that the registered handler did not set the result. Make sure it was set before inserting it.
+      if (!resp.callResult.result.IsNull()) {
+        json << resp.callResult.result;
+      } else {
+        // TODO: do we want to let it return null or by default we put success when there was no error and no result either. Maybe
+        // empty?
+        json << "success";
+      }
+    }
+
+    // ID. Only if present.
+    if (!resp.id.empty()) {
+      json << YAML::Key << "id" << YAML::Value << resp.id;
+    }
+    // else: We do not insert null as it will break the json, we need literal null and not ~ (as per yaml)
+    // json << YAML::Null;
+
+    json << YAML::EndMap;
+  }
+
+public:
+  ///
+  /// @brief Convert @see specs::RPCResponseInfo into a std::string.
+  ///
+  /// @param resp The rpc object to be converted @see specs::RPCResponseInfo
+  /// @return std::string The string representation of the response object
+  ///
+  static std::string
+  encode(const specs::RPCResponseInfo &resp)
+  {
+    YAML::Emitter json;
+    json << YAML::DoubleQuoted << YAML::Flow;
+    encode(resp, json);
+
+    return json.c_str();
+  }
+
+  ///
+  /// @brief Convert @see specs::RPCResponse into a std::string, this handled a batch response.
+  ///
+  /// @param response The object to be converted
+  /// @return std::string the string representation of the response object after being encode.
+  ///
+  static std::string
+  encode(const specs::RPCResponse &response)
+  {
+    YAML::Emitter json;
+    json << YAML::DoubleQuoted << YAML::Flow;
+    {
+      if (response.is_batch()) {
+        json << YAML::BeginSeq;
+      }
+
+      for (const auto &resp : response.get_messages()) {
+        encode(resp, json);
+      }
+
+      if (response.is_batch()) {
+        json << YAML::EndSeq;
+      }
+    }
+
+    return json.c_str();
+  }
+};
+} // namespace rpc::json_codecs
diff --git a/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc b/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc
new file mode 100644
index 0000000..24aa1fb
--- /dev/null
+++ b/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc
@@ -0,0 +1,609 @@
+/**
+  @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 <catch.hpp> /* catch unit-test framework */
+
+#include <tscore/BufferWriter.h>
+
+#include "rpc/jsonrpc/JsonRPCManager.h"
+#include "rpc/jsonrpc/JsonRPC.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+
+namespace
+{
+const int ErratId{1};
+
+// Not using the singleton logic.
+struct JsonRpcUnitTest : rpc::JsonRPCManager {
+  JsonRpcUnitTest() : JsonRPCManager() {}
+  using base = JsonRPCManager;
+  bool
+  remove_handler(std::string const &name)
+  {
+    return base::remove_handler(name);
+  }
+  template <typename Func>
+  bool
+  add_notification_handler(const std::string &name, Func &&call)
+  {
+    return base::add_notification_handler(name, std::forward<Func>(call), nullptr, {});
+  }
+  template <typename Func>
+  bool
+  add_method_handler(const std::string &name, Func &&call)
+  {
+    return base::add_method_handler(name, std::forward<Func>(call), nullptr, {});
+  }
+
+  std::optional<std::string>
+  handle_call(std::string const &jsonString)
+  {
+    return base::handle_call(rpc::Context{}, jsonString);
+  }
+};
+
+enum class TestErrors { ERR1 = 9999, ERR2 };
+inline ts::Rv<YAML::Node>
+test_callback_ok_or_error(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> resp;
+
+  // play with the req.id if needed.
+  if (YAML::Node n = params["return_error"]) {
+    auto yesOrNo = n.as<std::string>();
+    if (yesOrNo == "yes") {
+      resp.errata().push(ErratId, static_cast<int>(TestErrors::ERR1), "Just an error message to add more meaning to the failure");
+    } else {
+      resp.result()["ran"] = "ok";
+    }
+  }
+  return resp;
+}
+
+static int notificationCallCount{0};
+inline void
+test_nofitication(YAML::Node const &params)
+{
+  notificationCallCount++;
+}
+} // namespace
+TEST_CASE("Multiple Registrations - methods", "[methods]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("More than  one method")
+  {
+    REQUIRE(rpc.add_method_handler("test_callback_ok_or_error", &test_callback_ok_or_error));
+    REQUIRE(rpc.add_method_handler("test_callback_ok_or_error", &test_callback_ok_or_error) == false);
+  }
+}
+
+TEST_CASE("Multiple Registrations - notifications", "[notifications]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("inserting several notifications with the same name")
+  {
+    REQUIRE(rpc.add_notification_handler("test_nofitication", &test_nofitication));
+    REQUIRE(rpc.add_notification_handler("test_nofitication", &test_nofitication) == false);
+  }
+}
+
+TEST_CASE("Register/call method", "[method]")
+{
+  JsonRpcUnitTest rpc;
+
+  SECTION("Registring the method")
+  {
+    REQUIRE(rpc.add_method_handler("test_callback_ok_or_error", &test_callback_ok_or_error));
+
+    SECTION("Calling the method")
+    {
+      const auto json = rpc.handle_call(
+        R"({"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "no"}, "id": "13"})");
+      REQUIRE(json);
+      const std::string_view expected = R"({"jsonrpc": "2.0", "result": {"ran": "ok"}, "id": "13"})";
+      REQUIRE(*json == expected);
+    }
+  }
+}
+
+// VALID RESPONSES WITH CUSTOM ERRORS
+TEST_CASE("Register/call method - respond with errors (data field)", "[method][error.data]")
+{
+  JsonRpcUnitTest rpc;
+
+  SECTION("Registring the method")
+  {
+    REQUIRE(rpc.add_method_handler("test_callback_ok_or_error", &test_callback_ok_or_error));
+
+    SECTION("Calling the method")
+    {
+      const auto json = rpc.handle_call(
+        R"({"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "yes"}, "id": "14"})");
+      REQUIRE(json);
+      const std::string_view expected =
+        R"({"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution", "data": [{"code": 9999, "message": "Just an error message to add more meaning to the failure"}]}, "id": "14"})";
+      REQUIRE(*json == expected);
+    }
+  }
+}
+
+TEST_CASE("Register/call notification", "[notifications]")
+{
+  JsonRpcUnitTest rpc;
+
+  SECTION("Registring the notification")
+  {
+    REQUIRE(rpc.add_notification_handler("test_nofitication", &test_nofitication));
+
+    SECTION("Calling the notification")
+    {
+      rpc.handle_call(R"({"jsonrpc": "2.0", "method": "test_nofitication", "params": {"json": "rpc"}})");
+      REQUIRE(notificationCallCount == 1);
+      notificationCallCount = 0; // for further use.
+    }
+  }
+}
+
+TEST_CASE("Basic test, batch calls", "[methods][notifications]")
+{
+  JsonRpcUnitTest rpc;
+
+  SECTION("inserting multiple functions, mixed method and notifications.")
+  {
+    const auto f1_added = rpc.add_method_handler("test_callback_ok_or_error", &test_callback_ok_or_error);
+    const bool f2_added = rpc.add_notification_handler("test_nofitication", &test_nofitication);
+
+    REQUIRE(f1_added);
+    REQUIRE(f2_added);
+
+    SECTION("we call in batch, two functions and one notification")
+    {
+      const auto resp1 = rpc.handle_call(
+        R"([{"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "no"}, "id": "13"}
+      ,{"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "yes"}, "id": "14"}
+      ,{"jsonrpc": "2.0", "method": "test_nofitication", "params": {"name": "damian"}}])");
+
+      REQUIRE(resp1);
+      const std::string_view expected =
+        R"([{"jsonrpc": "2.0", "result": {"ran": "ok"}, "id": "13"}, {"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution", "data": [{"code": 9999, "message": "Just an error message to add more meaning to the failure"}]}, "id": "14"}])";
+      REQUIRE(*resp1 == expected);
+    }
+  }
+}
+
+TEST_CASE("Single registered notification", "[notifications]")
+{
+  notificationCallCount = 0;
+  JsonRpcUnitTest rpc;
+  SECTION("Single notification, only notifications in the batch.")
+  {
+    REQUIRE(rpc.add_notification_handler("test_nofitication", &test_nofitication));
+
+    SECTION("Call the notifications in batch")
+    {
+      const auto should_be_no_response = rpc.handle_call(
+        R"([{"jsonrpc": "2.0", "method": "test_nofitication", "params": {"name": "JSON"}},
+              {"jsonrpc": "2.0", "method": "test_nofitication", "params": {"name": "RPC"}},
+              {"jsonrpc": "2.0", "method": "test_nofitication", "params": {"name": "2.0"}}])");
+
+      REQUIRE(!should_be_no_response);
+      REQUIRE(notificationCallCount == 3);
+    }
+  }
+}
+
+TEST_CASE("Valid json but invalid messages", "[errors]")
+{
+  JsonRpcUnitTest rpc;
+
+  SECTION("Valid json but invalid protocol {}")
+  {
+    const auto resp = rpc.handle_call(R"({})");
+    REQUIRE(resp);
+    std::string_view expected = R"({"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}})";
+    REQUIRE(*resp == expected);
+  }
+
+  SECTION("Valid json but invalid protocol [{},{}] - batch response")
+  {
+    const auto resp = rpc.handle_call(R"([{},{}])");
+    REQUIRE(resp);
+    std::string_view expected =
+      R"([{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}}])";
+    REQUIRE(*resp == expected);
+  }
+}
+
+TEST_CASE("Invalid json messages", "[errors][invalid json]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("Invalid json in an attempt of batch")
+  {
+    const auto resp = rpc.handle_call(
+      R"([{"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "no"}, "id": "13"}
+      ,{"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "yes
+      ,{"jsonrpc": "2.0", "method": "test_nofitication", "params": {"name": "damian"}}])");
+
+    REQUIRE(resp);
+
+    std::string_view expected = R"({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}})";
+    REQUIRE(*resp == expected);
+  }
+}
+
+TEST_CASE("Invalid parameters base on the jsonrpc 2.0 protocol", "[protocol]")
+{
+  JsonRpcUnitTest rpc;
+
+  REQUIRE(rpc.add_method_handler("test_callback_ok_or_error", &test_callback_ok_or_error));
+  REQUIRE(rpc.add_notification_handler("test_nofitication", &test_nofitication));
+
+  SECTION("version field")
+  {
+    SECTION("number instead of a string")
+    {
+      // THIS WILL FAIL BASE ON THE YAMLCPP WAY TO TEST TYPES. We can get the number first, which will be ok and then fail, but
+      // seems not the right way to do it. ok for now.
+      [[maybe_unused]] const auto resp =
+        rpc.handle_call(R"({"jsonrpc": 2.0, "method": "test_callback_ok_or_error", "params": {"return_error": "no"}, "id": "13"})");
+
+      // REQUIRE(resp);
+
+      [[maybe_unused]] const std::string_view expected =
+        R"({"jsonrpc": "2.0", "error": {"code": 2, "message": "Invalid version type, should be a string"}, "id": "13"})";
+      // REQUIRE(*resp == expected);
+    }
+
+    SECTION("2.8 instead of 2.0")
+    {
+      const auto resp = rpc.handle_call(
+        R"({"jsonrpc": "2.8", "method": "test_callback_ok_or_error", "params": {"return_error": "no"}, "id": "15"})");
+
+      REQUIRE(resp);
+      const std::string_view expected =
+        R"({"jsonrpc": "2.0", "error": {"code": 1, "message": "Invalid version, 2.0 only"}, "id": "15"})";
+      REQUIRE(*resp == expected);
+    }
+  }
+  SECTION("method field")
+  {
+    SECTION("using a number")
+    {
+      // THIS WILL FAIL BASE ON THE YAMLCPP WAY TO TEST TYPES, there is no explicit way to test for a particular type, we can get
+      // the number first, then as it should not be converted we get the string instead, this seems rather not the best way to do
+      // it. ok for now.
+      [[maybe_unused]] const auto resp =
+        rpc.handle_call(R"({"jsonrpc": "2.0", "method": 123, "params": {"return_error": "no"}, "id": "14"})");
+
+      // REQUIRE(resp);
+      [[maybe_unused]] const std::string_view expected =
+        R"({"jsonrpc": "2.0", "error": {"code": 4, "message": "Invalid method type, should be a string"}, "id": "14"})";
+      // REQUIRE(*resp == expected);
+    }
+  }
+  SECTION("param field")
+  {
+    SECTION("Invalidtype")
+    {
+      const auto resp = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": 13, "id": "13"})");
+
+      REQUIRE(resp);
+      const std::string_view expected =
+        R"({"jsonrpc": "2.0", "error": {"code": 6, "message": "Invalid params type. A Structured value is expected"}, "id": "13"})";
+      REQUIRE(*resp == expected);
+    }
+  }
+
+  SECTION("id field")
+  {
+    SECTION("null id")
+    {
+      const auto resp = rpc.handle_call(
+        R"({"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "no"}, "id": null})");
+      REQUIRE(resp);
+      const std::string_view expected =
+        R"({"jsonrpc": "2.0", "error": {"code": 8, "message": "Use of null as id is discouraged"}})";
+      REQUIRE(*resp == expected);
+    }
+  }
+}
+
+TEST_CASE("Basic test with member functions(add, remove)", "[basic][member_functions]")
+{
+  struct TestMemberFunctionCall {
+    TestMemberFunctionCall() {}
+    bool
+    register_member_function_as_callback(JsonRpcUnitTest &rpc)
+    {
+      return rpc.add_method_handler(
+        "member_function",
+        [this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> { return test(id, req); });
+    }
+    ts::Rv<YAML::Node>
+    test(std::string_view const &id, const YAML::Node &req)
+    {
+      ts::Rv<YAML::Node> resp;
+      resp.result() = "grand!";
+      return resp;
+    }
+
+    // TODO: test notification as well.
+  };
+
+  SECTION("A RPC object and a custom class")
+  {
+    JsonRpcUnitTest rpc;
+    TestMemberFunctionCall tmfc;
+
+    REQUIRE(tmfc.register_member_function_as_callback(rpc) == true);
+    SECTION("call the member function")
+    {
+      const auto response = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "member_function", "id": "AbC"})");
+      REQUIRE(response);
+      REQUIRE(*response == R"({"jsonrpc": "2.0", "result": "grand!", "id": "AbC"})");
+    }
+
+    SECTION("We remove the callback handler")
+    {
+      REQUIRE(rpc.remove_handler("member_function") == true);
+
+      const auto response = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "member_function", "id": "AbC"})");
+      REQUIRE(response);
+      REQUIRE(*response == R"({"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "AbC"})");
+    }
+  }
+}
+
+TEST_CASE("Test Dispatcher rpc method", "[dispatcher]")
+{
+  JsonRpcUnitTest rpc;
+  const auto response = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "show_registered_handlers", "id": "AbC"})");
+  REQUIRE(*response ==
+          R"({"jsonrpc": "2.0", "result": {"methods": ["get_service_descriptor", "show_registered_handlers"]}, "id": "AbC"})");
+}
+
+[[maybe_unused]] static ts::Rv<YAML::Node>
+subtract(std::string_view const &id, YAML::Node const &numbers)
+{
+  ts::Rv<YAML::Node> res;
+
+  if (numbers.Type() == YAML::NodeType::Sequence) {
+    auto it   = numbers.begin();
+    int total = it->as<int>();
+    ++it;
+    for (; it != numbers.end(); ++it) {
+      total -= it->as<int>();
+    }
+
+    res.result() = total;
+  } else if (numbers.Type() == YAML::NodeType::Map) {
+    if (numbers["subtrahend"] && numbers["minuend"]) {
+      int total    = numbers["minuend"].as<int>() - numbers["subtrahend"].as<int>();
+      res.result() = total;
+    }
+  }
+  return res;
+}
+
+[[maybe_unused]] static ts::Rv<YAML::Node>
+sum(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> res;
+  int total{0};
+  for (auto n : params) {
+    total += n.as<int>();
+  }
+  res.result() = total;
+  return res;
+}
+
+[[maybe_unused]] static ts::Rv<YAML::Node>
+get_data(std::string_view const &id, YAML::Node const &params)
+{
+  ts::Rv<YAML::Node> res;
+  res.result().push_back("hello");
+  res.result().push_back("5");
+  return res;
+}
+
+[[maybe_unused]] static void
+update(YAML::Node const &params)
+{
+}
+[[maybe_unused]] static void
+foobar(YAML::Node const &params)
+{
+}
+[[maybe_unused]] static void
+notify_hello(YAML::Node const &params)
+{
+}
+
+// TODO: add tests base on the protocol example doc.
+TEST_CASE("Basic tests from the jsonrpc 2.0 doc.")
+{
+  SECTION("rpc call with positional parameters")
+  {
+    JsonRpcUnitTest rpc;
+
+    REQUIRE(rpc.add_method_handler("subtract", &subtract));
+    const auto resp = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "1"})");
+    REQUIRE(resp);
+    REQUIRE(*resp == R"({"jsonrpc": "2.0", "result": "19", "id": "1"})");
+
+    const auto resp1 = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": "1"})");
+    REQUIRE(resp1);
+    REQUIRE(*resp1 == R"({"jsonrpc": "2.0", "result": "-19", "id": "1"})");
+  }
+
+  SECTION("rpc call with named parameters")
+  {
+    JsonRpcUnitTest rpc;
+
+    REQUIRE(rpc.add_method_handler("subtract", &subtract));
+    const auto resp =
+      rpc.handle_call(R"({"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": "3"})");
+    REQUIRE(resp);
+    REQUIRE(*resp == R"({"jsonrpc": "2.0", "result": "19", "id": "3"})");
+
+    const auto resp1 =
+      rpc.handle_call(R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": "3"})");
+    REQUIRE(resp1);
+    REQUIRE(*resp1 == R"({"jsonrpc": "2.0", "result": "19", "id": "3"})");
+  }
+
+  SECTION("A notification")
+  {
+    JsonRpcUnitTest rpc;
+
+    REQUIRE(rpc.add_notification_handler("update", &update));
+    const auto resp = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]})");
+    REQUIRE(!resp);
+
+    REQUIRE(rpc.add_notification_handler("foobar", &foobar));
+    const auto resp1 = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "foobar"})");
+    REQUIRE(!resp1);
+  }
+
+  SECTION("rpc call of non-existent method")
+  {
+    JsonRpcUnitTest rpc;
+
+    const auto resp = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "foobar", "id": "1"})");
+    REQUIRE(resp);
+    REQUIRE(*resp == R"({"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"})");
+  }
+  SECTION("rpc call with invalid JSON")
+  {
+    JsonRpcUnitTest rpc;
+
+    const auto resp = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz])");
+    REQUIRE(resp);
+    REQUIRE(*resp == R"({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}})");
+  }
+  SECTION("rpc call with invalid Request object")
+  {
+    // We do have a custom object here, which will return invalid param object.
+    // skip it
+  }
+  SECTION("rpc call Batch, invalid JSON")
+  {
+    JsonRpcUnitTest rpc;
+
+    const auto resp =
+      rpc.handle_call(R"( {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method")");
+    REQUIRE(resp);
+    REQUIRE(*resp == R"({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}})");
+  }
+  SECTION("rpc call with an empty Array")
+  {
+    JsonRpcUnitTest rpc;
+    const auto resp = rpc.handle_call(R"([])");
+    REQUIRE(resp);
+    std::string_view expected = R"({"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}})";
+    REQUIRE(*resp == expected);
+  }
+  SECTION("rpc call with an invalid Batch")
+  {
+    JsonRpcUnitTest rpc;
+    const auto resp = rpc.handle_call(R"([1])");
+    REQUIRE(resp);
+    std::string_view expected = R"([{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}}])";
+    REQUIRE(*resp == expected);
+  }
+  SECTION("rpc call with invalid Batch")
+  {
+    JsonRpcUnitTest rpc;
+    const auto resp = rpc.handle_call(R"([1,2,3])");
+    REQUIRE(resp);
+    std::string expected = R"([{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}})"
+                           R"(, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}})"
+                           R"(, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}}])";
+    REQUIRE(*resp == expected);
+  }
+
+  SECTION("rpc call Batch(all notifications")
+  {
+    JsonRpcUnitTest rpc;
+    REQUIRE(rpc.add_notification_handler("notify_hello", &notify_hello));
+    REQUIRE(rpc.add_notification_handler("notify_sum", &notify_hello));
+    const auto resp = rpc.handle_call(
+      R"( [{"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]}, {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}])");
+    REQUIRE(!resp);
+  }
+}
+
+TEST_CASE("Handle un-handle handler's error", "[throw]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("Basic exception thrown")
+  {
+    REQUIRE(
+      rpc.add_method_handler("oops_i_did_it_again", [](std::string_view const &id, const YAML::Node &params) -> ts::Rv<YAML::Node> {
+        throw std::runtime_error("Oops, I did it again");
+      }));
+    const auto resp           = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "oops_i_did_it_again", "id": "1"})");
+    std::string_view expected = R"({"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution"}, "id": "1"})";
+    REQUIRE(*resp == expected);
+  }
+}
+
+TEST_CASE("Call registered method with no ID", "[no-id]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("Basic test, no id on method call")
+  {
+    REQUIRE(
+      rpc.add_method_handler("call_me_with_no_id", [](std::string_view const &id, const YAML::Node &params) -> ts::Rv<YAML::Node> {
+        throw std::runtime_error("Oops, I did it again");
+      }));
+    const auto resp           = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "call_me_with_no_id"})");
+    std::string_view expected = R"({"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}})";
+    REQUIRE(*resp == expected);
+  }
+}
+
+TEST_CASE("Call registered notification with ID", "[notification_and_id]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("Basic test, id on a notification call")
+  {
+    REQUIRE(rpc.add_notification_handler(
+      "call_me_with_id", [](const YAML::Node &params) -> void { throw std::runtime_error("Oops, I did it again"); }));
+    const auto resp           = rpc.handle_call(R"({"jsonrpc": "2.0", "method": "call_me_with_id", "id": "1"})");
+    std::string_view expected = R"({"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": "1"})";
+    REQUIRE(*resp == expected);
+  }
+}
+
+TEST_CASE("Call method with invalid ID", "[invalid_id]")
+{
+  JsonRpcUnitTest rpc;
+  SECTION("Basic test, invalid ids")
+  {
+    std::string req = R"([{"id": "", "jsonrpc": "2.0", "method": "will_not_pass_the_validation"},)"
+                      R"({"id": {}, "jsonrpc": "2.0", "method": "will_not_pass_the_validation"}])";
+    auto resp = rpc.handle_call(req);
+    std::string expected =
+      R"([{"jsonrpc": "2.0", "error": {"code": 11, "message": "Use of an empty string as id is discouraged"}}, )"
+      R"({"jsonrpc": "2.0", "error": {"code": 7, "message": "Invalid id type"}}])";
+    REQUIRE(*resp == expected);
+  }
+}
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/jsonrpc/unit_tests/unit_test_main.cc
similarity index 84%
copy from plugins/experimental/mysql_remap/default.h
copy to mgmt/rpc/jsonrpc/unit_tests/unit_test_main.cc
index 7cd5a84..4691595 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/jsonrpc/unit_tests/unit_test_main.cc
@@ -1,4 +1,6 @@
-/*
+/**
+  @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
@@ -7,16 +9,14 @@
   "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
+      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
-
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#define CATCH_CONFIG_MAIN
+#include <catch.hpp>
diff --git a/mgmt/rpc/schema/admin_lookup_records_params_schema.json b/mgmt/rpc/schema/admin_lookup_records_params_schema.json
new file mode 100644
index 0000000..92aa7ca
--- /dev/null
+++ b/mgmt/rpc/schema/admin_lookup_records_params_schema.json
@@ -0,0 +1,64 @@
+{
+  "$schema":"http://json-schema.org/draft-04/schema#",
+  "title":"Params field RPC Record request definition.",
+  "description":"This is the definition expected for a RPC record request. This should be used to obtain information regarding a particular record(s) from Traffic Server Licensed under Apache V2 https://www.apache.org/licenses/LICENSE-2.0",
+  "type":"array",
+  "items":{
+     "type":"object",
+     "properties":{
+        "record_name":{
+           "type":"string",
+           "note":[
+              "if requesting a regex, then use record_name_regex instead, only one can be used in the same object."
+           ]
+        },
+        "record_name_regex":{
+           "type":"string"
+        },
+        "rec_types":{
+           "type":"array",
+           "items":{
+              "anyOf":[
+                 {
+                    "type":"integer",
+                    "enum":[
+                       1,
+                       2,
+                       4,
+                       16,
+                       32,
+                       63
+                    ]
+                 },
+                 {
+                    "type":"string",
+                    "enum":[
+                       "1",
+                       "2",
+                       "4",
+                       "16",
+                       "32",
+                       "63"
+                    ]
+                 }
+              ],
+              "note":[
+                 "Config=1, Process=2, Node=4, Local=16, Plugin=32, All=63."
+              ]
+           }
+        }
+     },
+     "oneOf":[
+        {
+           "required":[
+              "record_name_regex"
+           ]
+        },
+        {
+           "required":[
+              "record_name"
+           ]
+        }
+     ]
+  }
+}
\ No newline at end of file
diff --git a/mgmt/rpc/schema/jsonrpc_request_schema.json b/mgmt/rpc/schema/jsonrpc_request_schema.json
new file mode 100644
index 0000000..4ca5e96
--- /dev/null
+++ b/mgmt/rpc/schema/jsonrpc_request_schema.json
@@ -0,0 +1,54 @@
+{
+   "$schema":"http://json-schema.org/draft-04/schema#",
+   "title":"JSONRPC 2.0 request schema",
+   "description":"Data for a JSON RPC 2.0 request, this may contains some exceptions to the standard. Licensed under Apache V2 https://www.apache.org/licenses/LICENSE-2.0",
+   "oneOf":[
+      {
+         "description":"An individual request",
+         "$ref":"#/definitions/request"
+      },
+      {
+         "description":"An array of requests",
+         "type":"array",
+         "items":{
+            "$ref":"#/definitions/request"
+         }
+      }
+   ],
+   "definitions":{
+      "request":{
+         "type":"object",
+         "required":[
+            "jsonrpc",
+            "method"
+         ],
+         "properties":{
+            "jsonrpc":{
+               "enum":[
+                  "2.0"
+               ]
+            },
+            "method":{
+               "type":"string"
+            },
+            "id":{
+               "type":[
+                  "string",
+                  "number",
+                  "null"
+               ],
+               "note":[
+                  "While allowed, null should be avoided: http://www.jsonrpc.org/specification#id1",
+                  "While allowed, a number with a fractional part should be avoided: http://www.jsonrpc.org/specification#id2"
+               ]
+            },
+            "params":{
+               "type":[
+                  "array",
+                  "object"
+               ]
+            }
+         }
+      }
+   }
+ }
diff --git a/mgmt/rpc/schema/jsonrpc_response_schema.json b/mgmt/rpc/schema/jsonrpc_response_schema.json
new file mode 100644
index 0000000..a851e79
--- /dev/null
+++ b/mgmt/rpc/schema/jsonrpc_response_schema.json
@@ -0,0 +1,104 @@
+{
+  "$schema":"http://json-schema.org/draft-04/schema#",
+  "title":"JSON RPC 2.0 response message",
+  "description":"Data for a JSON RPC 2.0 response, this may contains some exceptions to the standard which will be noted. Licensed under Apache V2 https://www.apache.org/licenses/LICENSE-2.0",
+  "oneOf":[
+     {
+        "$ref":"#/definitions/success"
+     },
+     {
+        "$ref":"#/definitions/error"
+     },
+     {
+        "type":"array",
+        "items":{
+           "oneOf":[
+              {
+                 "$ref":"#/definitions/success"
+              },
+              {
+                 "$ref":"#/definitions/error"
+              }
+           ]
+        }
+     }
+  ],
+  "definitions":{
+     "common":{
+        "required":[
+           "jsonrpc"
+        ],
+        "not":{
+           "description":"Cannot have result and error at the same time",
+           "required":[
+              "result",
+              "error"
+           ]
+        },
+        "type":"object",
+        "properties":{
+           "id":{
+              "type":[
+                 "string",
+                 "integer",
+                 "null"
+              ],
+              "note":[
+                 "Specs specify that this should be mandatory, in this implementation if the server cannot fetch the id on error, the id field will not be set.",
+                 "As per our implementation, the Server will not use literal null"
+               ]
+           },
+           "jsonrpc":{
+              "enum":[
+                 "2.0"
+              ]
+           }
+        }
+     },
+     "success":{
+        "description":"A success. The result member is then required and can be anything.",
+        "allOf":[
+           {
+              "$ref":"#/definitions/common"
+           },
+           {
+              "required":[
+                 "result"
+              ]
+           }
+        ]
+     },
+     "error":{
+        "allOf":[
+           {
+              "$ref":"#/definitions/common"
+           },
+           {
+              "required":[
+                 "error"
+              ],
+              "properties":{
+                 "error":{
+                    "type":"object",
+                    "required":[
+                       "code",
+                       "message"
+                    ],
+                    "properties":{
+                       "code":{
+                          "type":"integer"
+                       },
+                       "message":{
+                          "type":"string"
+                       },
+                       "data":{
+                          "description":"Optional, It will describe a list of events that occurred during the handling."
+                       }
+                    }
+                 }
+              }
+           }
+        ]
+     }
+  }
+}
diff --git a/mgmt/rpc/schema/success_response_schema.json b/mgmt/rpc/schema/success_response_schema.json
new file mode 100644
index 0000000..42b05d1
--- /dev/null
+++ b/mgmt/rpc/schema/success_response_schema.json
@@ -0,0 +1,6 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Success response schema",
+  "type": "string",
+  "enum":["success"]
+}
\ No newline at end of file
diff --git a/mgmt/rpc/server/CommBase.cc b/mgmt/rpc/server/CommBase.cc
new file mode 100644
index 0000000..acec776
--- /dev/null
+++ b/mgmt/rpc/server/CommBase.cc
@@ -0,0 +1,63 @@
+/**
+  @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 "CommBase.h"
+
+namespace
+{
+struct CommInternalErrorCategory : std::error_category {
+  const char *name() const noexcept override;
+  std::string message(int ev) const override;
+};
+
+const char *
+CommInternalErrorCategory::name() const noexcept
+{
+  return "comm_internal_error_category";
+}
+
+std::string
+CommInternalErrorCategory::message(int ev) const
+{
+  switch (static_cast<rpc::comm::InternalError>(ev)) {
+  case rpc::comm::InternalError::MAX_TRANSIENT_ERRORS_HANDLED:
+    return {"We've reach the maximun attempt on transient errors."};
+  case rpc::comm::InternalError::POLLIN_ERROR:
+    return {"We haven't got a POLLIN flag back while waiting"};
+  case rpc::comm::InternalError::PARTIAL_READ:
+    return {"No more data to be read, but the buffer contains some invalid? data."};
+  case rpc::comm::InternalError::FULL_BUFFER:
+    return {"Buffer's full."};
+  default:
+    return "Internal Communication Error" + std::to_string(ev);
+  }
+}
+
+const CommInternalErrorCategory commInternalErrorCategory{};
+} // anonymous namespace
+
+namespace rpc::comm
+{
+std::error_code
+make_error_code(rpc::comm::InternalError e)
+{
+  return {static_cast<int>(e), commInternalErrorCategory};
+}
+
+} // namespace rpc::comm
\ No newline at end of file
diff --git a/mgmt/rpc/server/CommBase.h b/mgmt/rpc/server/CommBase.h
new file mode 100644
index 0000000..5646477
--- /dev/null
+++ b/mgmt/rpc/server/CommBase.h
@@ -0,0 +1,47 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <system_error>
+
+#include <tscore/Errata.h>
+
+#include "rpc/config/JsonRPCConfig.h"
+
+namespace rpc::comm
+{
+struct BaseCommInterface {
+  virtual ~BaseCommInterface() {}
+  virtual bool configure(YAML::Node const &params) = 0;
+  virtual void run()                               = 0;
+  virtual std::error_code init()                   = 0;
+  virtual bool stop()                              = 0;
+  virtual std::string const &name() const          = 0;
+};
+
+enum class InternalError { MAX_TRANSIENT_ERRORS_HANDLED = 1, POLLIN_ERROR, PARTIAL_READ, FULL_BUFFER };
+std::error_code make_error_code(rpc::comm::InternalError e);
+
+} // namespace rpc::comm
+namespace std
+{
+template <> struct is_error_code_enum<rpc::comm::InternalError> : true_type {
+};
+} // namespace std
\ No newline at end of file
diff --git a/mgmt/rpc/server/IPCSocketServer.cc b/mgmt/rpc/server/IPCSocketServer.cc
new file mode 100644
index 0000000..df84a6d
--- /dev/null
+++ b/mgmt/rpc/server/IPCSocketServer.cc
@@ -0,0 +1,475 @@
+/**
+  @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 <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/file.h>
+#include <poll.h>
+
+#include <atomic>
+#include <thread>
+#include <memory>
+#include <optional>
+#include <chrono>
+#include <cerrno>
+#include <cstring>
+#include <system_error>
+#include <iostream>
+
+#include "tscore/Diags.h"
+#include "tscore/bwf_std_format.h"
+#include "records/I_RecProcess.h"
+#include "tscore/ink_sock.h"
+#include "utils/MgmtSocket.h"
+
+#include <ts/ts.h>
+
+#include "rpc/jsonrpc/JsonRPCManager.h"
+#include "rpc/server/IPCSocketServer.h"
+
+namespace
+{
+constexpr size_t MAX_REQUEST_BUFFER_SIZE{32000};
+constexpr auto logTag = "rpc.net";
+
+// Quick check for errors(base on the errno);
+bool check_for_transient_errors();
+
+// Check poll's return and validate against the passed function.
+template <typename Func>
+bool
+poll_on_socket(Func &&check_poll_return, std::chrono::milliseconds timeout, int fd)
+{
+  struct pollfd poll_fd;
+  poll_fd.fd     = fd;
+  poll_fd.events = POLLIN; // when data is ready.
+  int poll_ret;
+  do {
+    poll_ret = poll(&poll_fd, 1, timeout.count());
+  } while (check_poll_return(poll_ret));
+
+  if (!(poll_fd.revents & POLLIN)) {
+    return false;
+  }
+  return true;
+}
+} // namespace
+
+namespace rpc::comm
+{
+IPCSocketServer::~IPCSocketServer()
+{
+  unlink(_conf.sockPathName.c_str());
+}
+
+bool
+IPCSocketServer::configure(YAML::Node const &params)
+{
+  try {
+    _conf = params.as<Config>();
+  } catch (YAML::Exception const &ex) {
+    return false;
+  }
+
+  return true;
+}
+
+std::error_code
+IPCSocketServer::init()
+{
+  // Need to run some validations on the pathname to avoid issue. Normally this would not be an issue, but some tests may fail on
+  // this.
+  if (_conf.sockPathName.empty() || _conf.sockPathName.size() > sizeof _serverAddr.sun_path) {
+    Debug(logTag, "Invalid unix path name, check the size.");
+    return std::make_error_code(static_cast<std::errc>(ENAMETOOLONG));
+  }
+
+  std::error_code ec; // Flag possible errors.
+
+  if (this->create_socket(ec); ec) {
+    return ec;
+  }
+
+  Debug(logTag, "Using %s as socket path.", _conf.sockPathName.c_str());
+  _serverAddr.sun_family = AF_UNIX;
+  std::strncpy(_serverAddr.sun_path, _conf.sockPathName.c_str(), sizeof(_serverAddr.sun_path) - 1);
+
+  if (this->bind(ec); ec) {
+    this->close();
+    return ec;
+  }
+
+  if (this->listen(ec); ec) {
+    this->close();
+    return ec;
+  }
+
+  return ec;
+}
+
+bool
+IPCSocketServer::poll_for_new_client(std::chrono::milliseconds timeout) const
+{
+  auto check_poll_return = [&](int pfd) -> bool {
+    if (!_running.load()) {
+      return false;
+    }
+    if (pfd < 0) {
+      switch (errno) {
+      case EINTR:
+      case EAGAIN:
+        return true;
+      default:
+        return false;
+      }
+    } else if (pfd > 0) {
+      // ready.
+      return false;
+    } else {
+      // time out, try again
+      return true;
+    }
+  };
+
+  return poll_on_socket(check_poll_return, timeout, this->_socket);
+}
+
+void
+IPCSocketServer::run()
+{
+  _running.store(true);
+
+  ts::LocalBufferWriter<MAX_REQUEST_BUFFER_SIZE> bw;
+  while (_running) {
+    // poll till socket it's ready.
+    if (!this->poll_for_new_client()) {
+      if (_running.load()) {
+        Warning("ups, we've got an issue.");
+      }
+      break;
+    }
+
+    std::error_code ec;
+    if (auto fd = this->accept(ec); !ec) {
+      Client client{fd};
+
+      if (auto [ok, errStr] = client.read_all(bw); ok) {
+        const auto json = std::string{bw.data(), bw.size()};
+        rpc::Context ctx;
+        // we want to make sure the peer's credentials are ok.
+        ctx.get_auth().add_checker(
+          [&](TSRPCHandlerOptions const &opt, ts::Errata &errata) -> void { return late_check_peer_credentials(fd, opt, errata); });
+
+        if (auto response = rpc::JsonRPCManager::instance().handle_call(ctx, json); response) {
+          // seems a valid response.
+          if (client.write(*response, ec); ec) {
+            Debug(logTag, "Error sending the response: %s", ec.message().c_str());
+          }
+        } // it was a notification.
+      } else {
+        Debug(logTag, "Error detected while reading: %s", errStr.c_str());
+      }
+    } else {
+      Debug(logTag, "Error while accepting a new connection on the socket: %s", ec.message().c_str());
+    }
+
+    bw.reset();
+  }
+
+  this->close();
+}
+
+bool
+IPCSocketServer::stop()
+{
+  _running.store(false);
+
+  this->close();
+
+  return true;
+}
+
+void
+IPCSocketServer::create_socket(std::error_code &ec)
+{
+  _socket = socket(AF_UNIX, SOCK_STREAM, 0);
+
+  if (_socket < 0) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+  }
+}
+
+int
+IPCSocketServer::accept(std::error_code &ec) const
+{
+  int ret = {-1};
+
+  for (int retries = 0; retries < _conf.maxRetriesOnTransientErrors; retries++) {
+    ret = ::accept(_socket, 0, 0);
+    if (ret >= 0) {
+      return ret;
+    }
+    if (!check_for_transient_errors()) {
+      ec = std::make_error_code(static_cast<std::errc>(errno));
+      return ret;
+    }
+  }
+
+  if (ret < 0) {
+    // seems that we have reched the max retries.
+    ec = InternalError::MAX_TRANSIENT_ERRORS_HANDLED;
+  }
+
+  return ret;
+}
+
+void
+IPCSocketServer::bind(std::error_code &ec)
+{
+  int lock_fd = open(_conf.lockPathName.c_str(), O_RDONLY | O_CREAT, 0600);
+  if (lock_fd == -1) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+    return;
+  }
+
+  int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
+  if (ret != 0) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+    return;
+  }
+  // TODO: we may be able to use SO_REUSEADDR
+
+  // remove socket file
+  unlink(_conf.sockPathName.c_str());
+
+  ret = ::bind(_socket, (struct sockaddr *)&_serverAddr, sizeof(struct sockaddr_un));
+  if (ret < 0) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+    return;
+  }
+
+  // If the socket is not administratively restricted, check whether we have platform
+  // support. Otherwise, default to making it restricted.
+  bool restricted{true};
+  if (!_conf.restrictedAccessApi) {
+    restricted = !mgmt_has_peereid();
+  }
+
+  mode_t mode = restricted ? 00700 : 00777;
+  if (chmod(_conf.sockPathName.c_str(), mode) < 0) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+    return;
+  }
+}
+
+void
+IPCSocketServer::listen(std::error_code &ec)
+{
+  if (::listen(_socket, _conf.backlog) < 0) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+    return;
+  }
+}
+
+void
+IPCSocketServer::close()
+{
+  if (_socket > 0) {
+    ::close(_socket);
+    _socket = -1;
+  }
+}
+//// client
+
+IPCSocketServer::Client::Client(int fd) : _fd{fd} {}
+IPCSocketServer::Client::~Client()
+{
+  this->close();
+}
+
+// ---------------- client --------------
+bool
+IPCSocketServer::Client::poll_for_data(std::chrono::milliseconds timeout) const
+{
+  auto check_poll_return = [&](int pfd) -> bool {
+    if (pfd > 0) {
+      // something is ready.
+      return false;
+    } else if (pfd < 0) {
+      switch (errno) {
+      case EINTR:
+      case EAGAIN:
+        return true;
+      default:
+        return false;
+      }
+    } else { // timeout
+      return false;
+    }
+  };
+
+  return poll_on_socket(check_poll_return, timeout, this->_fd);
+}
+
+void
+IPCSocketServer::Client::close()
+{
+  if (_fd > 0) {
+    ::close(_fd);
+    _fd = -1;
+  }
+}
+
+ssize_t
+IPCSocketServer::Client::read(swoc::MemSpan<char> span) const
+{
+  return ::read(_fd, span.data(), span.size());
+}
+
+std::tuple<bool, std::string>
+IPCSocketServer::Client::read_all(ts::FixedBufferWriter &bw) const
+{
+  std::string buff;
+  while (bw.remaining() > 0) {
+    auto ret = read({bw.auxBuffer(), bw.remaining()});
+    if (ret < 0) {
+      if (check_for_transient_errors()) {
+        continue;
+      } else {
+        return {false, ts::bwprint(buff, "Error reading the socket: {}", ts::bwf::Errno{})};
+      }
+    }
+
+    if (ret == 0) {
+      if (bw.size()) {
+        return {false, ts::bwprint(buff, "Peer disconnected after reading {} bytes.", bw.size())};
+      }
+      return {false, ts::bwprint(buff, "Peer disconnected. EOF")};
+    }
+    bw.fill(ret);
+    if (bw.remaining() > 0) {
+      using namespace std::chrono_literals;
+      if (!this->poll_for_data(1ms)) {
+        return {true, buff};
+      }
+      continue;
+    } else {
+      ts::bwprint(buff, "Buffer is full, we hit the limit: {}", bw.capacity());
+      break;
+    }
+  }
+
+  return {false, buff};
+}
+
+void
+IPCSocketServer::Client::write(std::string const &data, std::error_code &ec) const
+{
+  if (::write(_fd, data.c_str(), data.size()) < 0) {
+    ec = std::make_error_code(static_cast<std::errc>(errno));
+  }
+}
+IPCSocketServer::Config::Config()
+{
+  // Set default values.
+  std::string rundir{RecConfigReadRuntimeDir()};
+  lockPathName = Layout::relative_to(rundir, "jsonrpc20.lock");
+  sockPathName = Layout::relative_to(rundir, "jsonrpc20.sock");
+}
+
+void
+IPCSocketServer::late_check_peer_credentials(int peedFd, TSRPCHandlerOptions const &options, ts::Errata &errata) const
+{
+  ts::LocalBufferWriter<256> w;
+  // For privileged calls, ensure we have caller credentials and that the caller is privileged.
+  if (mgmt_has_peereid() && options.auth.restricted) {
+    uid_t euid = -1;
+    gid_t egid = -1;
+    if (mgmt_get_peereid(peedFd, &euid, &egid) == -1) {
+      errata.push(1, static_cast<int>(UnauthorizedErrorCode::PEER_CREDENTIALS_ERROR),
+                  w.print("Error getting peer credentials: {}\0", ts::bwf::Errno{}).data());
+    } else if (euid != 0 && euid != geteuid()) {
+      errata.push(1, static_cast<int>(UnauthorizedErrorCode::PERMISSION_DENIED),
+                  w.print("Denied privileged API access for uid={} gid={}\0", euid, egid).data());
+    }
+  }
+}
+
+} // namespace rpc::comm
+
+namespace YAML
+{
+template <> struct convert<rpc::comm::IPCSocketServer::Config> {
+  using config = rpc::comm::IPCSocketServer::Config;
+
+  static bool
+  decode(const Node &node, config &rhs)
+  {
+    // ++ If we configure this, traffic_ctl will not be able to connect.
+    // ++ This is meant to be used by unit test as you need to set up  a
+    // ++ server.
+    if (auto n = node[config::LOCK_PATH_NAME_KEY_STR]) {
+      rhs.lockPathName = n.as<std::string>();
+    }
+    if (auto n = node[config::SOCK_PATH_NAME_KEY_STR]) {
+      rhs.sockPathName = n.as<std::string>();
+    }
+
+    if (auto n = node[config::BACKLOG_KEY_STR]) {
+      rhs.backlog = n.as<int>();
+    }
+    if (auto n = node[config::MAX_RETRY_ON_TR_ERROR_KEY_STR]) {
+      rhs.maxRetriesOnTransientErrors = n.as<int>();
+    }
+    if (auto n = node[config::RESTRICTED_API]) {
+      rhs.restrictedAccessApi = n.as<bool>();
+    }
+    return true;
+  }
+};
+} // namespace YAML
+
+namespace
+{
+bool
+check_for_transient_errors()
+{
+  switch (errno) {
+  case EINTR:
+  case EAGAIN:
+
+#ifdef ENOMEM
+  case ENOMEM:
+#endif
+
+#ifdef ENOBUF
+  case ENOBUF:
+#endif
+
+#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
+  case EWOULDBLOCK:
+#endif
+    return true;
+  default:
+    return false;
+  }
+}
+} // namespace
diff --git a/mgmt/rpc/server/IPCSocketServer.h b/mgmt/rpc/server/IPCSocketServer.h
new file mode 100644
index 0000000..3a76bf4
--- /dev/null
+++ b/mgmt/rpc/server/IPCSocketServer.h
@@ -0,0 +1,144 @@
+/* @file
+   @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.
+*/
+
+#pragma once
+
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <string_view>
+#include <memory>
+
+#include "swoc/MemSpan.h"
+#include "tscore/BufferWriter.h"
+#include "tscore/I_Layout.h"
+
+#include "rpc/server/CommBase.h"
+#include "rpc/config/JsonRPCConfig.h"
+
+namespace rpc::comm
+{
+///
+/// @brief Unix Domain Socket implementation that deals with the JSON RPC call handling mechanism.
+///
+/// Very basic and straight forward implementation of an unix socket domain. The implementation
+/// follows the \link BaseCommInterface.
+///
+/// @note The server will keep reading the client's requests till the buffer is full or there is no more data in the wire.
+///       Buffer size = 32k
+class IPCSocketServer : public BaseCommInterface
+{
+  // Error codes to track any unauthorized call to a rpc handler.
+  enum class UnauthorizedErrorCode {
+    PEER_CREDENTIALS_ERROR = 1, ///< Error while trying to read the peer credentials from the unix socket.
+    PERMISSION_DENIED      = 2  ///< Client's socket credential didn't wasn't sufficient to execute the method.
+  };
+  ///
+  /// @brief Connection abstraction class that deals with sending and receiving data from the connected peer.
+  ///
+  /// When client goes out of scope it will close the socket. If you want to keep the socket connected, you need to keep
+  /// the client object around.
+  struct Client {
+    /// @param fd Peer's socket.
+    Client(int fd);
+    /// Destructor will close the socket(if opened);
+    ~Client();
+
+    /// Close the passed socket (if opened);
+    void close();
+    /// Reads from the socket, this function calls the system read function.
+    /// @return the size of what was read by the read() function.
+    ssize_t read(swoc::MemSpan<char> span) const;
+    /// Function that reads all the data available in the socket, it will validate the data on every read if there is more than
+    /// a single chunk.
+    /// The size of the buffer to be read is not defined in this function, but rather passed in the @c bw parameter.
+    /// @return A tuple with a boolean flag indicating if the operation did success or not, in case of any error, a text will
+    /// be added with a description.
+    std::tuple<bool, std::string> read_all(ts::FixedBufferWriter &bw) const;
+    /// Write the the socket with the passed data.
+    /// @return std::error_code.
+    void write(std::string const &data, std::error_code &ec) const;
+    bool is_connected() const;
+
+  private:
+    /// Wait for data to be ready for reading.
+    /// @return true if the data is ready, false otherwise.
+    bool poll_for_data(std::chrono::milliseconds timeout) const;
+    int _fd; ///< connected peer's socket.
+  };
+
+public:
+  IPCSocketServer() = default;
+  virtual ~IPCSocketServer() override;
+
+  /// Configure the  local socket.
+  bool configure(YAML::Node const &params) override;
+  /// This function will create the socket, bind it and make  it listen to the new socket.
+  /// @return the std::error_code with the collected error(if any)
+  std::error_code init() override;
+
+  void run() override;
+  bool stop() override;
+
+  std::string const &
+  name() const override
+  {
+    return _name;
+  }
+
+protected: // unit test access
+  struct Config {
+    Config();
+    static constexpr auto SOCK_PATH_NAME_KEY_STR{"sock_path_name"};
+    static constexpr auto LOCK_PATH_NAME_KEY_STR{"lock_path_name"};
+    static constexpr auto BACKLOG_KEY_STR{"backlog"};
+    static constexpr auto MAX_RETRY_ON_TR_ERROR_KEY_STR{"max_retry_on_transient_errors"};
+    static constexpr auto RESTRICTED_API{"restricted_api"};
+    // is it safe to call Layout now?
+    std::string sockPathName;
+    std::string lockPathName;
+
+    int backlog{5};
+    int maxRetriesOnTransientErrors{64};
+    bool restrictedAccessApi{
+      true}; // This config value will drive the permissions of the jsonrpc socket(either 0700(default) or 0777).
+  };
+
+  friend struct YAML::convert<rpc::comm::IPCSocketServer::Config>;
+  Config _conf;
+
+private:
+  inline static const std::string _name = "Local Socket";
+  bool poll_for_new_client(std::chrono::milliseconds timeout = std::chrono::milliseconds(1000)) const;
+  void create_socket(std::error_code &ec);
+
+  int accept(std::error_code &ec) const;
+  void bind(std::error_code &ec);
+  void listen(std::error_code &ec);
+  void close();
+  void late_check_peer_credentials(int peedFd, TSRPCHandlerOptions const &options, ts::Errata &errata) const;
+
+  std::atomic_bool _running;
+
+  struct sockaddr_un _serverAddr;
+  int _socket{-1};
+};
+} // namespace rpc::comm
diff --git a/mgmt/rpc/server/RPCServer.cc b/mgmt/rpc/server/RPCServer.cc
new file mode 100644
index 0000000..2117ee2
--- /dev/null
+++ b/mgmt/rpc/server/RPCServer.cc
@@ -0,0 +1,90 @@
+/**
+  @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 "RPCServer.h"
+#include "rpc/server/IPCSocketServer.h"
+
+rpc::RPCServer *jsonrpcServer = nullptr;
+
+namespace rpc
+{
+static const auto logTag{"rpc"};
+
+RPCServer::RPCServer(config::RPCConfig const &conf)
+{
+  switch (conf.get_comm_type()) {
+  case config::RPCConfig::CommType::UNIX: {
+    _socketImpl = std::make_unique<comm::IPCSocketServer>();
+    if (!_socketImpl->configure(conf.get_comm_config_params())) {
+      Debug(logTag, "Unable to configure the socket: Stick to the default configuration.");
+    }
+  } break;
+  default:;
+    throw std::runtime_error("Unsupported communication type.");
+  };
+
+  // make sure we can initialize it.
+  if (auto ec = _socketImpl->init(); ec) {
+    throw std::runtime_error(ec.message());
+  }
+}
+
+std::string_view
+RPCServer::selected_comm_name() const noexcept
+{
+  return _socketImpl->name();
+}
+
+RPCServer::~RPCServer()
+{
+  stop_thread();
+}
+
+void * /* static */
+RPCServer::run_thread(void *a)
+{
+  void *ret = a;
+  if (jsonrpcServer->_init) {
+    jsonrpcServer->_rpcThread = jsonrpcServer->_init();
+  }
+  jsonrpcServer->_socketImpl->run();
+  Debug(logTag, "Socket stopped");
+  return ret;
+}
+
+void
+RPCServer::start_thread(std::function<TSThread()> const &cb_init, std::function<void(TSThread)> const &cb_destroy)
+{
+  Debug(logTag, "Starting RPC Server on: %s", _socketImpl->name().c_str());
+  _init    = cb_init;
+  _destroy = cb_destroy;
+
+  ink_thread_create(&_this_thread, run_thread, nullptr, 0, 0, nullptr);
+}
+
+void
+RPCServer::stop_thread()
+{
+  _socketImpl->stop();
+
+  ink_thread_join(_this_thread);
+  _this_thread = ink_thread_null();
+  Debug(logTag, "Stopping RPC server on: %s", _socketImpl->name().c_str());
+}
+} // namespace rpc
diff --git a/mgmt/rpc/server/RPCServer.h b/mgmt/rpc/server/RPCServer.h
new file mode 100644
index 0000000..a94e88a
--- /dev/null
+++ b/mgmt/rpc/server/RPCServer.h
@@ -0,0 +1,90 @@
+/**
+  @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.
+*/
+#pragma once
+
+#include <atomic>
+#include <thread>
+#include <memory>
+
+#include "tscore/Diags.h"
+
+#include "tscore/ink_thread.h"
+#include "tscore/ink_mutex.h"
+
+#include "tscore/ink_apidefs.h"
+#include <ts/apidefs.h>
+#include "rpc/jsonrpc/JsonRPCManager.h"
+#include "rpc/config/JsonRPCConfig.h"
+
+namespace rpc
+{
+namespace comm
+{
+  struct BaseCommInterface;
+}
+
+///
+/// @brief RPC Server implementation for the JSONRPC Logic. This class holds a transport which implements @see
+/// BaseCommInterface
+/// Objects of this class can start @see start_thread , stop @see stop_thread the server at any? time. More than one instance of
+/// this class can be created as long as they use different transport configuration.
+class RPCServer
+{
+public:
+  RPCServer() = default;
+  ///
+  /// @brief Construct a new Rpc Server object
+  ///        This function have one main goal, select the transport type base on the configuration and  initialize it.
+  ///
+  /// @throw std::runtime_error if:
+  ///        1 - It the configured transport isn't valid for the server to create it, then an exception will be thrown.
+  ///        2 - The transport layer cannot be initialized.
+  /// @param conf the configuration object.
+  ///
+  RPCServer(config::RPCConfig const &conf);
+
+  /// @brief The destructor will join the thread.
+  ~RPCServer();
+
+  /// @brief Returns a descriptive name that was set by the transport. Check @see BaseCommInterface
+  std::string_view selected_comm_name() const noexcept;
+
+  /// @brief Thread function that runs the transport.
+  void start_thread(std::function<TSThread()> const &cb_init        = std::function<TSThread()>(),
+                    std::function<void(TSThread)> const &cb_destroy = std::function<void(TSThread)>());
+
+  /// @brief Function to stop the transport and join the thread to finish.
+  void stop_thread();
+
+private:
+  /// @brief Actual thread routine. This will start the socket.
+  static void *run_thread(void *);
+
+  std::function<TSThread()> _init;
+  std::function<void(TSThread)> _destroy;
+  ink_thread _this_thread{ink_thread_null()};
+  TSThread _rpcThread{nullptr};
+
+  std::thread running_thread;
+  std::unique_ptr<comm::BaseCommInterface> _socketImpl;
+};
+} // namespace rpc
+
+extern rpc::RPCServer *jsonrpcServer;
diff --git a/mgmt/rpc/server/unit_tests/test_rpcserver.cc b/mgmt/rpc/server/unit_tests/test_rpcserver.cc
new file mode 100644
index 0000000..7649b3b
--- /dev/null
+++ b/mgmt/rpc/server/unit_tests/test_rpcserver.cc
@@ -0,0 +1,558 @@
+/**
+  @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.
+*/
+#define CATCH_CONFIG_EXTERNAL_INTERFACES
+
+#include <catch.hpp> /* catch unit-test framework */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+
+#include <thread>
+#include <future>
+#include <chrono>
+#include <fstream>
+
+#include <tscore/BufferWriter.h>
+#include <tscore/ts_file.h>
+#include "ts/ts.h"
+
+#include "rpc/jsonrpc/JsonRPC.h"
+#include "rpc/server/RPCServer.h"
+#include "rpc/server/IPCSocketServer.h"
+
+#include "shared/rpc/IPCSocketClient.h"
+#include "I_EventSystem.h"
+#include "tscore/I_Layout.h"
+#include "diags.i"
+
+#define DEFINE_JSONRPC_PROTO_FUNCTION(fn) ts::Rv<YAML::Node> fn(std::string_view const &id, const YAML::Node &params)
+
+namespace fs = ts::file;
+
+namespace rpc
+{
+bool
+test_remove_handler(std::string_view name)
+{
+  return rpc::JsonRPCManager::instance().remove_handler(name);
+}
+
+template <typename Func>
+inline bool
+add_method_handler(const std::string &name, Func &&call)
+{
+  return rpc::JsonRPCManager::instance().add_method_handler(name, std::forward<Func>(call), nullptr, {});
+}
+} // namespace rpc
+static const std::string sockPath{"/tmp/jsonrpc20_test.sock"};
+static const std::string lockPath{"/tmp/jsonrpc20_test.lock"};
+static constexpr int default_backlog{5};
+static constexpr int default_maxRetriesOnTransientErrors{64};
+static constexpr auto logTag{"rpc.test.client"};
+
+struct RPCServerTestListener : Catch::TestEventListenerBase {
+  using TestEventListenerBase::TestEventListenerBase; // inherit constructor
+  ~RPCServerTestListener();
+
+  // The whole test run starting
+  void
+  testRunStarting(Catch::TestRunInfo const &testRunInfo) override
+  {
+    Layout::create();
+    init_diags("rpc|rpc.test", nullptr);
+    RecProcessInit(RECM_STAND_ALONE);
+
+    signal(SIGPIPE, SIG_IGN);
+
+    ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION);
+    eventProcessor.start(2, 1048576);
+
+    // EThread *main_thread = new EThread;
+    main_thread = std::make_unique<EThread>();
+    main_thread->set_specific();
+
+    rpc::config::RPCConfig serverConfig;
+
+    auto confStr{R"({"rpc": { "enabled": true, "unix": { "lock_path_name": ")" + lockPath + R"(", "sock_path_name": ")" + sockPath +
+                 R"(",  "backlog": 5,"max_retry_on_transient_errors": 64 }}})"};
+    YAML::Node configNode = YAML::Load(confStr);
+    serverConfig.load(configNode["rpc"]);
+    try {
+      jsonrpcServer = new rpc::RPCServer(serverConfig);
+
+      jsonrpcServer->start_thread();
+    } catch (std::exception const &ex) {
+      Debug(logTag, "Oops: %s", ex.what());
+    }
+  }
+
+  // The whole test run ending
+  void
+  testRunEnded(Catch::TestRunStats const &testRunStats) override
+  {
+    // jsonrpcServer->stop_thread();
+    // delete main_thread;
+    if (jsonrpcServer) {
+      delete jsonrpcServer;
+    }
+  }
+
+private:
+  // std::unique_ptr<rpc::RPCServer> jrpcServer;
+  std::unique_ptr<EThread> main_thread;
+};
+CATCH_REGISTER_LISTENER(RPCServerTestListener)
+
+RPCServerTestListener::~RPCServerTestListener() {}
+
+DEFINE_JSONRPC_PROTO_FUNCTION(some_foo) // id, params
+{
+  ts::Rv<YAML::Node> resp;
+  int dur{1};
+  try {
+    dur = params["duration"].as<int>();
+  } catch (...) {
+  }
+  INFO("Sleeping for " << dur << "s");
+  std::this_thread::sleep_for(std::chrono::seconds(dur));
+  resp.result()["res"]      = "ok";
+  resp.result()["duration"] = dur;
+
+  INFO("Done sleeping");
+  return resp;
+}
+namespace
+{
+/* Create and return a path to a temporary sandbox directory. */
+fs::path
+getTemporaryDir()
+{
+  std::error_code ec;
+  fs::path tmpDir = fs::canonical(fs::temp_directory_path(), ec);
+  tmpDir /= "sandbox_XXXXXX";
+
+  char dirNameTemplate[tmpDir.string().length() + 1];
+  sprintf(dirNameTemplate, "%s", tmpDir.c_str());
+
+  return fs::path(mkdtemp(dirNameTemplate));
+}
+
+// Handy class to avoid manually disconecting the socket.
+// TODO: should it also connect?
+struct ScopedLocalSocket : shared::rpc::IPCSocketClient {
+  using super = shared::rpc::IPCSocketClient;
+  // TODO, use another path.
+  ScopedLocalSocket() : IPCSocketClient(sockPath) {}
+  ~ScopedLocalSocket() { IPCSocketClient::disconnect(); }
+
+  template <std::size_t N>
+  void
+  send_in_chunks(std::string_view data, int disconnect_after_chunk_n = -1)
+  {
+    int chunk_number{1};
+    auto chunks = chunk<N>(data);
+    for (auto &&part : chunks) {
+      if (::write(_sock, part.c_str(), part.size()) < 0) {
+        Debug(logTag, "error sending message :%s", std ::strerror(errno));
+        break;
+      }
+
+      if (disconnect_after_chunk_n == chunk_number) {
+        Debug(logTag, "Disconnecting it after chunk %d", chunk_number);
+        super::disconnect();
+        return;
+      }
+      ++chunk_number;
+    }
+  }
+
+  // basic read, if fail, why it fail is irrelevant in this test.
+  std::string
+  read()
+  {
+    ts::LocalBufferWriter<32000> bw;
+    auto ret = super::read_all(bw);
+    if (ret == ReadStatus::NO_ERROR) {
+      return {bw.data(), bw.size()};
+    }
+    return {};
+  }
+  // small wrapper function to deal with the bw.
+  std::string
+  query(std::string_view msg)
+  {
+    ts::LocalBufferWriter<32000> bw;
+    auto ret = connect().send(msg).read_all(bw);
+    if (ret == ReadStatus::NO_ERROR) {
+      return {bw.data(), bw.size()};
+    }
+
+    return {};
+  }
+
+private:
+  template <typename Iter, std::size_t N>
+  std::array<std::string, N>
+  chunk_impl(Iter from, Iter to)
+  {
+    const std::size_t size = std::distance(from, to);
+    if (size <= N) {
+      return {std::string{from, to}};
+    }
+    std::size_t index{0};
+    std::array<std::string, N> ret;
+    const std::size_t each_part = size / N;
+    const std::size_t remainder = size % N;
+
+    for (auto it = from; it != to;) {
+      if (std::size_t rem = std::distance(it, to); rem == (each_part + remainder)) {
+        ret[index++] = std::string{it, it + rem};
+        break;
+      }
+      ret[index++] = std::string{it, it + each_part};
+      std::advance(it, each_part);
+    }
+
+    return ret;
+  }
+
+  template <std::size_t N>
+  auto
+  chunk(std::string_view v)
+  {
+    return chunk_impl<std::string_view::const_iterator, N>(v.begin(), v.end());
+  }
+};
+
+// helper function to send a request and update the promise when the response is done.
+// This is to be used in a multithread test.
+void
+send_request(std::string json, std::promise<std::string> p)
+{
+  ScopedLocalSocket rpc_client;
+  auto resp = rpc_client.query(json);
+  p.set_value(resp);
+}
+} // namespace
+TEST_CASE("Sending 'concurrent' requests to the rpc server.", "[thread]")
+{
+  SECTION("A registered handlers")
+  {
+    rpc::add_method_handler("some_foo", &some_foo);
+    rpc::add_method_handler("some_foo2", &some_foo);
+
+    std::promise<std::string> p1;
+    std::promise<std::string> p2;
+    auto fut1 = p1.get_future();
+    auto fut2 = p2.get_future();
+
+    REQUIRE_NOTHROW([&]() {
+      // Two different clients, on the same server, as the server is an Unix Domain Socket, it should handle all this
+      // properly, in any case we just run the basic smoke test for our server.
+      auto t1 = std::thread(&send_request, R"({"jsonrpc": "2.0", "method": "some_foo", "params": {"duration": 1}, "id": "aBcD"})",
+                            std::move(p1));
+      auto t2 = std::thread(&send_request, R"({"jsonrpc": "2.0", "method": "some_foo", "params": {"duration": 1}, "id": "eFgH"})",
+                            std::move(p2));
+      // wait to get the promise set.
+      fut1.wait();
+      fut2.wait();
+
+      // the expected
+      std::string_view expected1{R"({"jsonrpc": "2.0", "result": {"res": "ok", "duration": "1"}, "id": "aBcD"})"};
+      std::string_view expected2{R"({"jsonrpc": "2.0", "result": {"res": "ok", "duration": "1"}, "id": "eFgH"})"};
+
+      CHECK(fut1.get() == expected1);
+      CHECK(fut2.get() == expected2);
+
+      t1.join();
+      t2.join();
+    }());
+  }
+}
+
+std::string
+random_string(std::string::size_type length)
+{
+  auto randchar = []() -> char {
+    const char charset[] = "0123456789"
+                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz";
+    const size_t max_index = (sizeof(charset) - 1);
+    return charset[rand() % max_index];
+  };
+  std::string str(length, 0);
+  std::generate_n(str.begin(), length, randchar);
+  return str;
+}
+
+DEFINE_JSONRPC_PROTO_FUNCTION(do_nothing) // id, params, resp
+{
+  ts::Rv<YAML::Node> resp;
+  resp.result()["size"] = params["msg"].as<std::string>().size();
+  return resp;
+}
+
+TEST_CASE("Basic message sending to a running server", "[socket]")
+{
+  REQUIRE(rpc::add_method_handler("do_nothing", &do_nothing));
+  SECTION("Basic single request to the rpc server")
+  {
+    const int S{500};
+    auto json{R"({"jsonrpc": "2.0", "method": "do_nothing", "params": {"msg":")" + random_string(S) + R"("}, "id":"EfGh-1"})"};
+    REQUIRE_NOTHROW([&]() {
+      ScopedLocalSocket rpc_client;
+      auto resp = rpc_client.query(json);
+
+      REQUIRE(resp == R"({"jsonrpc": "2.0", "result": {"size": ")" + std::to_string(S) + R"("}, "id": "EfGh-1"})");
+    }());
+  }
+  REQUIRE(rpc::test_remove_handler("do_nothing"));
+}
+
+TEST_CASE("Sending a message bigger than the internal server's buffer. 32000", "[buffer][error]")
+{
+  REQUIRE(rpc::add_method_handler("do_nothing", &do_nothing));
+
+  SECTION("Message larger than the the accepted size.")
+  {
+    const int S{32000}; // + the rest of the json message.
+    auto json{R"({"jsonrpc": "2.0", "method": "do_nothing", "params": {"msg":")" + random_string(S) + R"("}, "id":"EfGh-1"})"};
+    REQUIRE_NOTHROW([&]() {
+      ScopedLocalSocket rpc_client;
+      auto resp = rpc_client.query(json);
+      REQUIRE(resp.empty());
+    }());
+  }
+
+  REQUIRE(rpc::test_remove_handler("do_nothing"));
+}
+
+TEST_CASE("Test with invalid json message", "[socket]")
+{
+  REQUIRE(rpc::add_method_handler("do_nothing", &do_nothing));
+
+  SECTION("A rpc server")
+  {
+    const int S{10};
+    auto json{R"({"jsonrpc": "2.0", "method": "do_nothing", "params": { "msg": ")" + random_string(S) + R"("}, "id": "EfGh})"};
+    REQUIRE_NOTHROW([&]() {
+      ScopedLocalSocket rpc_client;
+      auto resp = rpc_client.query(json);
+
+      CHECK(resp == R"({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}})");
+    }());
+  }
+  REQUIRE(rpc::test_remove_handler("do_nothing"));
+}
+
+TEST_CASE("Test with chunks", "[socket][chunks]")
+{
+  REQUIRE(rpc::add_method_handler("do_nothing", &do_nothing));
+
+  SECTION("Sending request by chunks")
+  {
+    const int S{10};
+    auto json{R"({"jsonrpc": "2.0", "method": "do_nothing", "params": { "msg": ")" + random_string(S) +
+              R"("}, "id": "chunk-parts-3"})"};
+
+    REQUIRE_NOTHROW([&]() {
+      ScopedLocalSocket rpc_client;
+      using namespace std::chrono_literals;
+      rpc_client.connect();
+      rpc_client.send_in_chunks<3>(json);
+      auto resp = rpc_client.read();
+      REQUIRE(resp == R"({"jsonrpc": "2.0", "result": {"size": ")" + std::to_string(S) + R"("}, "id": "chunk-parts-3"})");
+    }());
+  }
+  REQUIRE(rpc::test_remove_handler("do_nothing"));
+}
+
+TEST_CASE("Test with chunks - disconnect after second part", "[socket][chunks]")
+{
+  REQUIRE(rpc::add_method_handler("do_nothing", &do_nothing));
+
+  SECTION("Sending request by chunks")
+  {
+    const int S{4000};
+    auto json{R"({"jsonrpc": "2.0", "method": "do_nothing", "params": { "msg": ")" + random_string(S) +
+              R"("}, "id": "chunk-parts-3-2"})"};
+
+    REQUIRE_NOTHROW([&]() {
+      ScopedLocalSocket rpc_client;
+      using namespace std::chrono_literals;
+      rpc_client.connect();
+      rpc_client.send_in_chunks<3>(json, 2);
+      // read will fail.
+      auto resp = rpc_client.read();
+      REQUIRE(resp == "");
+    }());
+  }
+  REQUIRE(rpc::test_remove_handler("do_nothing"));
+}
+
+TEST_CASE("Test with chunks - incomplete message", "[socket][chunks]")
+{
+  REQUIRE(rpc::add_method_handler("do_nothing", &do_nothing));
+
+  SECTION("Sending request by chunks, broken message")
+  {
+    const int S{50};
+    auto json{R"({"jsonrpc": "2.0", "method": "do_nothing", "params": { "msg": ")" + random_string(S) +
+              R"("}, "id": "chunk-parts-3)"};
+    //                                  ^  missing-> "}
+
+    REQUIRE_NOTHROW([&]() {
+      ScopedLocalSocket rpc_client;
+      using namespace std::chrono_literals;
+      rpc_client.connect();
+      rpc_client.send_in_chunks<3>(json);
+      auto resp = rpc_client.read();
+      REQUIRE(resp == R"({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}})");
+    }());
+  }
+  REQUIRE(rpc::test_remove_handler("do_nothing"));
+}
+
+// Enable toggle
+TEST_CASE("Test rpc enable toggle feature - default enabled.", "[default values]")
+{
+  rpc::config::RPCConfig serverConfig;
+  REQUIRE(serverConfig.is_enabled() == true);
+}
+
+TEST_CASE("Test rpc enable toggle feature. Enabled by configuration", "[rpc][enabled]")
+{
+  rpc::config::RPCConfig serverConfig;
+
+  auto confStr{R"({"rpc": {"enabled": true}})"};
+  std::cout << "'" << confStr << "'" << std::endl;
+  YAML::Node configNode = YAML::Load(confStr);
+  serverConfig.load(configNode["rpc"]);
+  REQUIRE(serverConfig.is_enabled() == true);
+}
+
+TEST_CASE("Test rpc  enable toggle feature. Disabled by configuration", "[rpc][disabled]")
+{
+  rpc::config::RPCConfig serverConfig;
+
+  auto confStr{R"({"rpc": {"enabled":false}})"};
+
+  REQUIRE_NOTHROW([&]() {
+    YAML::Node configNode = YAML::Load(confStr);
+    serverConfig.load(configNode["rpc"]);
+  }());
+  REQUIRE(serverConfig.is_enabled() == false);
+}
+
+// TEST UDS Server configuration
+namespace
+{
+namespace trp = rpc::comm;
+// This class is defined to get access to the protected config object inside the IPCSocketServer class.
+struct LocalSocketTest : public trp::IPCSocketServer {
+  inline static const std::string _name = "LocalSocketTest";
+  bool
+  configure(YAML::Node const &params) override
+  {
+    return trp::IPCSocketServer::configure(params);
+  }
+  void
+  run() override
+  {
+  }
+  std::error_code
+  init() override
+  {
+    return trp::IPCSocketServer::init();
+  }
+  bool
+  stop() override
+  {
+    return true;
+  }
+  std::string const &
+  name() const override
+  {
+    return _name;
+  }
+  trp::IPCSocketServer::Config const &
+  get_conf() const
+  {
+    return _conf;
+  }
+};
+} // namespace
+
+TEST_CASE("Test configuration parsing. UDS values", "[string]")
+{
+  rpc::config::RPCConfig serverConfig;
+
+  auto confStr{R"({"rpc": { "enabled": true, "unix": { "lock_path_name": ")" + lockPath + R"(", "sock_path_name": ")" + sockPath +
+               R"(",  "backlog": 5,"max_retry_on_transient_errors": 64 }}})"};
+  YAML::Node configNode = YAML::Load(confStr);
+  serverConfig.load(configNode["rpc"]);
+
+  REQUIRE(serverConfig.get_comm_type() == rpc::config::RPCConfig::CommType::UNIX);
+
+  auto socket    = std::make_unique<LocalSocketTest>();
+  auto const ret = socket->configure(serverConfig.get_comm_config_params());
+  REQUIRE(ret);
+  REQUIRE(socket->get_conf().backlog == default_backlog);
+  REQUIRE(socket->get_conf().maxRetriesOnTransientErrors == default_maxRetriesOnTransientErrors);
+  REQUIRE(socket->get_conf().sockPathName == sockPath);
+  REQUIRE(socket->get_conf().lockPathName == lockPath);
+}
+
+TEST_CASE("Test configuration parsing from a file. UDS Server", "[file]")
+{
+  fs::path sandboxDir = getTemporaryDir();
+  fs::path configPath = sandboxDir / "jsonrpc.yaml";
+
+  // define here to later compare.
+  std::string sockPathName{configPath.string() + "jsonrpc20_test2.sock"};
+  std::string lockPathName{configPath.string() + "jsonrpc20_test2.lock"};
+
+  auto confStr{R"({"rpc": { "enabled": true, "unix": { "lock_path_name": ")" + lockPathName + R"(", "sock_path_name": ")" +
+               sockPathName + R"(",  "backlog": 5,"max_retry_on_transient_errors": 64 }}})"};
+  // write the config.
+  std::ofstream ofs(configPath.string(), std::ofstream::out);
+  // Yes, we write json into the yaml, remember, YAML is a superset of JSON, yaml parser can handle this.
+  ofs << confStr;
+  ofs.close();
+
+  rpc::config::RPCConfig serverConfig;
+  // on any error reading the file, default values will be used.
+  serverConfig.load_from_file(configPath.string());
+
+  REQUIRE(serverConfig.get_comm_type() == rpc::config::RPCConfig::CommType::UNIX);
+
+  auto socket     = std::make_unique<LocalSocketTest>();
+  auto const &ret = socket->configure(serverConfig.get_comm_config_params());
+  REQUIRE(ret);
+  REQUIRE(socket->get_conf().backlog == 5);
+  REQUIRE(socket->get_conf().maxRetriesOnTransientErrors == 64);
+  REQUIRE(socket->get_conf().sockPathName == sockPathName);
+  REQUIRE(socket->get_conf().lockPathName == lockPathName);
+
+  std::error_code ec;
+  REQUIRE(fs::remove(sandboxDir, ec));
+}
diff --git a/plugins/experimental/mysql_remap/default.h b/mgmt/rpc/server/unit_tests/unit_test_main.cc
similarity index 89%
rename from plugins/experimental/mysql_remap/default.h
rename to mgmt/rpc/server/unit_tests/unit_test_main.cc
index 7cd5a84..76e51e0 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/mgmt/rpc/server/unit_tests/unit_test_main.cc
@@ -1,4 +1,6 @@
-/*
+/**
+  @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
@@ -16,7 +18,5 @@
   limitations under the License.
 */
 
-#pragma once
-
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+#define CATCH_CONFIG_MAIN
+#include <catch.hpp>
diff --git a/mgmt/utils/Makefile.am b/mgmt/utils/Makefile.am
index 610d1fd..32e226e 100644
--- a/mgmt/utils/Makefile.am
+++ b/mgmt/utils/Makefile.am
@@ -25,6 +25,7 @@
 	-I$(abs_top_srcdir)/proxy \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
+        @SWOC_INCLUDES@ \
 	$(TS_INCLUDES) \
 	@OPENSSL_INCLUDES@
 
@@ -37,8 +38,6 @@
 TESTS = $(check_PROGRAMS)
 
 libutils_COMMON = \
-	MgmtMarshall.cc \
-	MgmtMarshall.h \
 	MgmtSocket.cc \
 	MgmtSocket.h \
 	MgmtUtils.cc \
@@ -47,26 +46,24 @@
 libutils_lm_la_SOURCES = \
 	$(libutils_COMMON) \
 	ExpandingArray.cc \
-	ExpandingArray.h \
-	MgmtLocalCleanup.cc
+	ExpandingArray.h
 
 libutils_p_la_SOURCES = \
-	$(libutils_COMMON) \
-	MgmtProcessCleanup.cc
+	$(libutils_COMMON)
 
 test_mgmt_utils_CPPFLAGS = $(AM_CPPFLAGS) -I$(abs_top_srcdir)/tests/include
 
 test_mgmt_utils_LDFLAGS = \
 	@AM_LDFLAGS@ \
-	@OPENSSL_LDFLAGS@
+	@SWOC_LDFLAGS@ @OPENSSL_LDFLAGS@
 
 test_mgmt_utils_SOURCES = \
-	unit_tests/unit_test_main.cc \
-	unit_tests/test_marshall.cc
+	unit_tests/unit_test_main.cc
 
 test_mgmt_utils_LDADD = \
 	libutils_p.la \
-	$(top_builddir)/src/tscore/libtscore.la
+	$(top_builddir)/src/tscore/libtscore.la \
+        @SWOC_LIBS@
 
 include $(top_srcdir)/build/tidy.mk
 
diff --git a/mgmt/utils/MgmtLocalCleanup.cc b/mgmt/utils/MgmtLocalCleanup.cc
deleted file mode 100644
index c57e343..0000000
--- a/mgmt/utils/MgmtLocalCleanup.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @file
-
-  Management cleanup for the local manager.
-
-  @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 "MgmtUtils.h"
-#include "LocalManager.h"
-
-void
-mgmt_cleanup()
-{
-  if (lmgmt != nullptr) {
-    lmgmt->mgmtShutdown();
-  }
-}
diff --git a/mgmt/utils/MgmtMarshall.cc b/mgmt/utils/MgmtMarshall.cc
deleted file mode 100644
index 61d6f4e..0000000
--- a/mgmt/utils/MgmtMarshall.cc
+++ /dev/null
@@ -1,532 +0,0 @@
-/** @file
-
-  Management packet marshalling.
-
-  @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 "tscore/ink_platform.h"
-#include "tscore/ink_memory.h"
-#include "tscore/ink_assert.h"
-#include "MgmtMarshall.h"
-#include "MgmtSocket.h"
-
-union MgmtMarshallAnyPtr {
-  MgmtMarshallInt *m_int;
-  MgmtMarshallLong *m_long;
-  MgmtMarshallString *m_string;
-  MgmtMarshallData *m_data;
-  void *m_void;
-};
-
-static char *empty = const_cast<char *>("");
-
-static bool
-data_is_nul_terminated(const MgmtMarshallData *data)
-{
-  const char *str = static_cast<const char *>(data->ptr);
-
-  ink_assert(str);
-  if (str[data->len - 1] != '\0') {
-    return false;
-  }
-
-  if (strlen(str) != (data->len - 1)) {
-    return false;
-  }
-
-  return true;
-}
-
-static ssize_t
-socket_read_bytes(int fd, void *buf, size_t needed)
-{
-  size_t nread = 0;
-
-  // makes sure the descriptor is readable
-  if (mgmt_read_timeout(fd, MAX_TIME_WAIT, 0) <= 0) {
-    return -1;
-  }
-
-  while (needed > nread) {
-    ssize_t ret = read(fd, buf, needed - nread);
-
-    if (ret < 0) {
-      if (mgmt_transient_error()) {
-        continue;
-      } else {
-        return -1;
-      }
-    }
-
-    if (ret == 0) {
-      // End of file before reading the remaining bytes.
-      errno = ECONNRESET;
-      return -1;
-    }
-
-    buf = static_cast<uint8_t *>(buf) + ret;
-    nread += ret;
-  }
-
-  return nread;
-}
-
-static ssize_t
-socket_write_bytes(int fd, const void *buf, ssize_t bytes)
-{
-  ssize_t nwritten = 0;
-
-  // makes sure the descriptor is writable
-  if (mgmt_write_timeout(fd, MAX_TIME_WAIT, 0) <= 0) {
-    return -1;
-  }
-
-  // write until we fulfill the number
-  while (nwritten < bytes) {
-    ssize_t ret = write(fd, buf, bytes - nwritten);
-    if (ret < 0) {
-      if (mgmt_transient_error()) {
-        continue;
-      }
-      return -1;
-    }
-
-    buf = (uint8_t *)buf + ret;
-    nwritten += ret;
-  }
-
-  return nwritten;
-}
-
-static ssize_t
-socket_write_buffer(int fd, const MgmtMarshallData *data)
-{
-  ssize_t nwrite;
-
-  nwrite = socket_write_bytes(fd, &(data->len), 4);
-  if (nwrite != 4) {
-    goto fail;
-  }
-
-  if (data->len) {
-    nwrite = socket_write_bytes(fd, data->ptr, data->len);
-    if (nwrite != static_cast<ssize_t>(data->len)) {
-      goto fail;
-    }
-  }
-
-  return data->len + 4;
-
-fail:
-  return -1;
-}
-
-static ssize_t
-socket_read_buffer(int fd, MgmtMarshallData *data)
-{
-  ssize_t nread;
-
-  ink_zero(*data);
-
-  nread = socket_read_bytes(fd, &(data->len), 4);
-  if (nread != 4) {
-    goto fail;
-  }
-
-  if (data->len) {
-    data->ptr = ats_malloc(data->len);
-    nread     = socket_read_bytes(fd, data->ptr, data->len);
-    if (nread != static_cast<ssize_t>(data->len)) {
-      goto fail;
-    }
-  }
-
-  return data->len + 4;
-
-fail:
-  ats_free(data->ptr);
-  ink_zero(*data);
-  return -1;
-}
-
-static ssize_t
-buffer_read_buffer(const uint8_t *buf, size_t len, MgmtMarshallData *data)
-{
-  ink_zero(*data);
-
-  if (len < 4) {
-    goto fail;
-  }
-
-  memcpy(&(data->len), buf, 4);
-  buf += 4;
-  len -= 4;
-
-  if (len < data->len) {
-    goto fail;
-  }
-
-  if (data->len) {
-    data->ptr = ats_malloc(data->len);
-    memcpy(data->ptr, buf, data->len);
-  }
-
-  return data->len + 4;
-
-fail:
-  ats_free(data->ptr);
-  ink_zero(*data);
-  return -1;
-}
-
-MgmtMarshallInt
-mgmt_message_length(const MgmtMarshallType *fields, unsigned count, ...)
-{
-  MgmtMarshallInt length;
-  va_list ap;
-
-  va_start(ap, count);
-  length = mgmt_message_length_v(fields, count, ap);
-  va_end(ap);
-
-  return length;
-}
-
-MgmtMarshallInt
-mgmt_message_length_v(const MgmtMarshallType *fields, unsigned count, va_list ap)
-{
-  MgmtMarshallAnyPtr ptr;
-  MgmtMarshallInt nbytes = 0;
-
-  for (unsigned n = 0; n < count; ++n) {
-    switch (fields[n]) {
-    case MGMT_MARSHALL_INT:
-      ptr.m_int = va_arg(ap, MgmtMarshallInt *);
-      nbytes += 4;
-      break;
-    case MGMT_MARSHALL_LONG:
-      ptr.m_long = va_arg(ap, MgmtMarshallLong *);
-      nbytes += 8;
-      break;
-    case MGMT_MARSHALL_STRING:
-      nbytes += 4;
-      ptr.m_string = va_arg(ap, MgmtMarshallString *);
-      if (*ptr.m_string == nullptr) {
-        ptr.m_string = &empty;
-      }
-      nbytes += strlen(*ptr.m_string) + 1;
-      break;
-    case MGMT_MARSHALL_DATA:
-      nbytes += 4;
-      ptr.m_data = va_arg(ap, MgmtMarshallData *);
-      nbytes += ptr.m_data->len;
-      break;
-    default:
-      errno = EINVAL;
-      return -1;
-    }
-  }
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_write(int fd, const MgmtMarshallType *fields, unsigned count, ...)
-{
-  ssize_t nbytes;
-  va_list ap;
-
-  va_start(ap, count);
-  nbytes = mgmt_message_write_v(fd, fields, count, ap);
-  va_end(ap);
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_write_v(int fd, const MgmtMarshallType *fields, unsigned count, va_list ap)
-{
-  MgmtMarshallAnyPtr ptr;
-  ssize_t nbytes = 0;
-
-  for (unsigned n = 0; n < count; ++n) {
-    ssize_t nwritten = 0;
-
-    switch (fields[n]) {
-    case MGMT_MARSHALL_INT:
-      ptr.m_int = va_arg(ap, MgmtMarshallInt *);
-      nwritten  = socket_write_bytes(fd, ptr.m_void, 4);
-      break;
-    case MGMT_MARSHALL_LONG:
-      ptr.m_long = va_arg(ap, MgmtMarshallLong *);
-      nwritten   = socket_write_bytes(fd, ptr.m_void, 8);
-      break;
-    case MGMT_MARSHALL_STRING: {
-      MgmtMarshallData data;
-      ptr.m_string = va_arg(ap, MgmtMarshallString *);
-      if (*ptr.m_string == nullptr) {
-        ptr.m_string = &empty;
-      }
-      data.ptr = *ptr.m_string;
-      data.len = strlen(*ptr.m_string) + 1;
-      nwritten = socket_write_buffer(fd, &data);
-      break;
-    }
-    case MGMT_MARSHALL_DATA:
-      ptr.m_data = va_arg(ap, MgmtMarshallData *);
-      nwritten   = socket_write_buffer(fd, ptr.m_data);
-      break;
-    default:
-      errno = EINVAL;
-      return -1;
-    }
-
-    if (nwritten == -1) {
-      return -1;
-    }
-
-    nbytes += nwritten;
-  }
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_read(int fd, const MgmtMarshallType *fields, unsigned count, ...)
-{
-  ssize_t nbytes;
-  va_list ap;
-
-  va_start(ap, count);
-  nbytes = mgmt_message_read_v(fd, fields, count, ap);
-  va_end(ap);
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_read_v(int fd, const MgmtMarshallType *fields, unsigned count, va_list ap)
-{
-  MgmtMarshallAnyPtr ptr;
-  ssize_t nbytes = 0;
-
-  for (unsigned n = 0; n < count; ++n) {
-    ssize_t nread;
-
-    switch (fields[n]) {
-    case MGMT_MARSHALL_INT:
-      ptr.m_int = va_arg(ap, MgmtMarshallInt *);
-      nread     = socket_read_bytes(fd, ptr.m_void, 4);
-      break;
-    case MGMT_MARSHALL_LONG:
-      ptr.m_long = va_arg(ap, MgmtMarshallLong *);
-      nread      = socket_read_bytes(fd, ptr.m_void, 8);
-      break;
-    case MGMT_MARSHALL_STRING: {
-      MgmtMarshallData data;
-
-      nread = socket_read_buffer(fd, &data);
-      if (nread == -1) {
-        break;
-      }
-
-      ink_assert(data_is_nul_terminated(&data));
-      ptr.m_string  = va_arg(ap, MgmtMarshallString *);
-      *ptr.m_string = static_cast<char *>(data.ptr);
-      break;
-    }
-    case MGMT_MARSHALL_DATA:
-      ptr.m_data = va_arg(ap, MgmtMarshallData *);
-      nread      = socket_read_buffer(fd, ptr.m_data);
-      break;
-    default:
-      errno = EINVAL;
-      return -1;
-    }
-
-    if (nread == -1) {
-      return -1;
-    }
-
-    nbytes += nread;
-  }
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_marshall(void *buf, size_t remain, const MgmtMarshallType *fields, unsigned count, ...)
-{
-  ssize_t nbytes = 0;
-  va_list ap;
-
-  va_start(ap, count);
-  nbytes = mgmt_message_marshall_v(buf, remain, fields, count, ap);
-  va_end(ap);
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_marshall_v(void *buf, size_t remain, const MgmtMarshallType *fields, unsigned count, va_list ap)
-{
-  MgmtMarshallAnyPtr ptr;
-  ssize_t nbytes = 0;
-
-  for (unsigned n = 0; n < count; ++n) {
-    ssize_t nwritten = 0;
-
-    switch (fields[n]) {
-    case MGMT_MARSHALL_INT:
-      if (remain < 4) {
-        goto nospace;
-      }
-      ptr.m_int = va_arg(ap, MgmtMarshallInt *);
-      memcpy(buf, ptr.m_int, 4);
-      nwritten = 4;
-      break;
-    case MGMT_MARSHALL_LONG:
-      if (remain < 8) {
-        goto nospace;
-      }
-      ptr.m_long = va_arg(ap, MgmtMarshallLong *);
-      memcpy(buf, ptr.m_long, 8);
-      nwritten = 8;
-      break;
-    case MGMT_MARSHALL_STRING: {
-      MgmtMarshallData data;
-      ptr.m_string = va_arg(ap, MgmtMarshallString *);
-      if (*ptr.m_string == nullptr) {
-        ptr.m_string = &empty;
-      }
-
-      data.ptr = *ptr.m_string;
-      data.len = strlen(*ptr.m_string) + 1;
-
-      if (remain < (4 + data.len)) {
-        goto nospace;
-      }
-
-      memcpy(buf, &data.len, 4);
-      memcpy(static_cast<uint8_t *>(buf) + 4, data.ptr, data.len);
-      nwritten = 4 + data.len;
-      break;
-    }
-    case MGMT_MARSHALL_DATA:
-      ptr.m_data = va_arg(ap, MgmtMarshallData *);
-      if (remain < (4 + ptr.m_data->len)) {
-        goto nospace;
-      }
-      memcpy(buf, &(ptr.m_data->len), 4);
-      memcpy(static_cast<uint8_t *>(buf) + 4, ptr.m_data->ptr, ptr.m_data->len);
-      nwritten = 4 + ptr.m_data->len;
-      break;
-    default:
-      errno = EINVAL;
-      return -1;
-    }
-
-    nbytes += nwritten;
-    buf = static_cast<uint8_t *>(buf) + nwritten;
-    remain -= nwritten;
-  }
-
-  return nbytes;
-
-nospace:
-  errno = EMSGSIZE;
-  return -1;
-}
-
-ssize_t
-mgmt_message_parse(const void *buf, size_t len, const MgmtMarshallType *fields, unsigned count, ...)
-{
-  MgmtMarshallInt nbytes = 0;
-  va_list ap;
-
-  va_start(ap, count);
-  nbytes = mgmt_message_parse_v(buf, len, fields, count, ap);
-  va_end(ap);
-
-  return nbytes;
-}
-
-ssize_t
-mgmt_message_parse_v(const void *buf, size_t len, const MgmtMarshallType *fields, unsigned count, va_list ap)
-{
-  MgmtMarshallAnyPtr ptr;
-  ssize_t nbytes = 0;
-
-  for (unsigned n = 0; n < count; ++n) {
-    ssize_t nread;
-
-    switch (fields[n]) {
-    case MGMT_MARSHALL_INT:
-      if (len < 4) {
-        goto nospace;
-      }
-      ptr.m_int = va_arg(ap, MgmtMarshallInt *);
-      memcpy(ptr.m_int, buf, 4);
-      nread = 4;
-      break;
-    case MGMT_MARSHALL_LONG:
-      if (len < 8) {
-        goto nospace;
-      }
-      ptr.m_long = va_arg(ap, MgmtMarshallLong *);
-      memcpy(ptr.m_int, buf, 8);
-      nread = 8;
-      break;
-    case MGMT_MARSHALL_STRING: {
-      MgmtMarshallData data;
-      nread = buffer_read_buffer(static_cast<const uint8_t *>(buf), len, &data);
-      if (nread == -1) {
-        goto nospace;
-      }
-
-      ink_assert(data_is_nul_terminated(&data));
-
-      ptr.m_string  = va_arg(ap, MgmtMarshallString *);
-      *ptr.m_string = static_cast<char *>(data.ptr);
-      break;
-    }
-    case MGMT_MARSHALL_DATA:
-      ptr.m_data = va_arg(ap, MgmtMarshallData *);
-      nread      = buffer_read_buffer(static_cast<const uint8_t *>(buf), len, ptr.m_data);
-      if (nread == -1) {
-        goto nospace;
-      }
-      break;
-    default:
-      errno = EINVAL;
-      return -1;
-    }
-
-    nbytes += nread;
-    buf = (uint8_t *)buf + nread;
-    len -= nread;
-  }
-
-  return nbytes;
-
-nospace:
-  errno = EMSGSIZE;
-  return -1;
-}
diff --git a/mgmt/utils/MgmtMarshall.h b/mgmt/utils/MgmtMarshall.h
deleted file mode 100644
index 91bc257..0000000
--- a/mgmt/utils/MgmtMarshall.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/** @file
-
-  Management packet marshalling.
-
-  @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.
- */
-
-#pragma once
-
-#include <cstdarg>
-
-#define MAX_TIME_WAIT 60 // num secs for a timeout on a select call (remote only)
-
-// Simple message marshalling.
-//
-// MGMT_MARSHALL_INT
-// Wire size is 4 bytes signed. This type is used for enum and boolean values, as well as embedded lengths and general
-// integer values.
-//
-// MGMT_MARSHALL_LONG
-// Wire size is 8 bytes signed.
-//
-// MGMT_MARSHALL_STRING
-// Wire size is a 4 byte length followed by N bytes. The trailing NUL is always sent and NULL strings are sent as empty
-// strings. This means that the minimum wire size for a string is 5 bytes (4 byte length + NUL byte). The unmarshalled
-// string point is guaranteed to be non-NULL.
-//
-// MGMT_MARSHALL_DATA
-// Wire size is 4 byte length followed by N data bytes. If the length is 0, no subsequent bytes are sent. In this case
-// the unmarshalled data pointer is guaranteed to be NULL.
-//
-enum MgmtMarshallType {
-  MGMT_MARSHALL_INT,    // int32_t
-  MGMT_MARSHALL_LONG,   // int64_t
-  MGMT_MARSHALL_STRING, // NUL-terminated string
-  MGMT_MARSHALL_DATA    // byte buffer
-};
-
-typedef int32_t MgmtMarshallInt;
-typedef int64_t MgmtMarshallLong;
-typedef char *MgmtMarshallString;
-
-struct MgmtMarshallData {
-  void *ptr;
-  size_t len;
-};
-
-MgmtMarshallInt mgmt_message_length(const MgmtMarshallType *fields, unsigned count, ...);
-MgmtMarshallInt mgmt_message_length_v(const MgmtMarshallType *fields, unsigned count, va_list ap);
-
-ssize_t mgmt_message_read(int fd, const MgmtMarshallType *fields, unsigned count, ...);
-ssize_t mgmt_message_read_v(int fd, const MgmtMarshallType *fields, unsigned count, va_list ap);
-
-ssize_t mgmt_message_write(int fd, const MgmtMarshallType *fields, unsigned count, ...);
-ssize_t mgmt_message_write_v(int fd, const MgmtMarshallType *fields, unsigned count, va_list ap);
-
-ssize_t mgmt_message_parse(const void *ptr, size_t len, const MgmtMarshallType *fields, unsigned count, ...);
-ssize_t mgmt_message_parse_v(const void *ptr, size_t len, const MgmtMarshallType *fields, unsigned count, va_list ap);
-
-ssize_t mgmt_message_marshall(void *ptr, size_t len, const MgmtMarshallType *fields, unsigned count, ...);
-ssize_t mgmt_message_marshall_v(void *ptr, size_t len, const MgmtMarshallType *fields, unsigned count, va_list ap);
diff --git a/mgmt/utils/MgmtProcessCleanup.cc b/mgmt/utils/MgmtProcessCleanup.cc
deleted file mode 100644
index e46e2be..0000000
--- a/mgmt/utils/MgmtProcessCleanup.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-/** @file
-
-  Management cleanup for the process manager.
-
-  @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 "MgmtUtils.h"
-
-void
-mgmt_cleanup()
-{
-  // No cleanup to do with a process manager
-}
diff --git a/mgmt/utils/MgmtUtils.cc b/mgmt/utils/MgmtUtils.cc
index 5cae4bc..f073660 100644
--- a/mgmt/utils/MgmtUtils.cc
+++ b/mgmt/utils/MgmtUtils.cc
@@ -26,8 +26,6 @@
 #include "MgmtSocket.h"
 #include "tscore/Diags.h"
 
-#include "LocalManager.h"
-
 static int use_syslog = 0;
 
 /* mgmt_use_syslog()
@@ -221,112 +219,14 @@
   return bytes_written;
 }
 
-void
-mgmt_log(const char *message_format, ...)
-{
-  va_list ap;
-  char extended_format[4096], message[4096];
-
-  va_start(ap, message_format);
-  if (diags()) {
-    NoteV(message_format, ap);
-  } else {
-    if (use_syslog) {
-      snprintf(extended_format, sizeof(extended_format), "log ==> %s", message_format);
-      vsnprintf(message, sizeof(message), extended_format, ap);
-      syslog(LOG_WARNING, "%s", message);
-    } else {
-      snprintf(extended_format, sizeof(extended_format), "[E. Mgmt] log ==> %s", message_format);
-      vsnprintf(message, sizeof(message), extended_format, ap);
-      ink_assert(fwrite(message, strlen(message), 1, stderr) == 1);
-    }
-  }
-
-  va_end(ap);
-  return;
-} /* End mgmt_log */
-
-void
-mgmt_elog(const int lerrno, const char *message_format, ...)
-{
-  va_list ap;
-  char extended_format[4096], message[4096];
-
-  va_start(ap, message_format);
-
-  if (diags()) {
-    ErrorV(message_format, ap);
-    if (lerrno != 0) {
-      Error("last system error %d: %s", lerrno, strerror(lerrno));
-    }
-  } else {
-    if (use_syslog) {
-      snprintf(extended_format, sizeof(extended_format), "ERROR ==> %s", message_format);
-      vsnprintf(message, sizeof(message), extended_format, ap);
-      syslog(LOG_ERR, "%s", message);
-      if (lerrno != 0) {
-        syslog(LOG_ERR, " (last system error %d: %s)", lerrno, strerror(lerrno));
-      }
-    } else {
-      snprintf(extended_format, sizeof(extended_format), "Manager ERROR: %s", message_format);
-      vsnprintf(message, sizeof(message), extended_format, ap);
-      ink_assert(fwrite(message, strlen(message), 1, stderr) == 1);
-      if (lerrno != 0) {
-        snprintf(message, sizeof(message), "(last system error %d: %s)", lerrno, strerror(lerrno));
-        ink_assert(fwrite(message, strlen(message), 1, stderr) == 1);
-      }
-    }
-  }
-  va_end(ap);
-  return;
-} /* End mgmt_elog */
-
-void
-mgmt_fatal(const int lerrno, const char *message_format, ...)
-{
-  va_list ap;
-
-  va_start(ap, message_format);
-
-  if (diags()) {
-    if (lerrno != 0) {
-      Error("last system error %d: %s", lerrno, strerror(lerrno));
-    }
-
-    FatalV(message_format, ap);
-  } else {
-    char extended_format[4096], message[4096];
-    snprintf(extended_format, sizeof(extended_format), "FATAL ==> %s", message_format);
-    vsnprintf(message, sizeof(message), extended_format, ap);
-
-    ink_assert(fwrite(message, strlen(message), 1, stderr) == 1);
-
-    if (use_syslog) {
-      syslog(LOG_ERR, "%s", message);
-    }
-
-    if (lerrno != 0) {
-      fprintf(stderr, "[E. Mgmt] last system error %d: %s", lerrno, strerror(lerrno));
-
-      if (use_syslog) {
-        syslog(LOG_ERR, " (last system error %d: %s)", lerrno, strerror(lerrno));
-      }
-    }
-  }
-
-  va_end(ap);
-
-  mgmt_cleanup();
-  ::exit(1);
-} /* End mgmt_fatal */
-
 static inline int
 get_interface_mtu(int sock_fd, struct ifreq *ifr)
 {
   if (ioctl(sock_fd, SIOCGIFMTU, ifr) < 0) {
-    mgmt_log("[getAddrForIntr] Unable to obtain MTU for "
-             "interface '%s'",
-             ifr->ifr_name);
+    Debug("mgmt_utils",
+          "[getAddrForIntr] Unable to obtain MTU for "
+          "interface '%s'",
+          ifr->ifr_name);
     return 0;
   } else {
 #if defined(solaris) || defined(hpux)
@@ -357,7 +257,7 @@
   memset(addr, 0, sizeof(struct in_addr));
 
   if ((fakeSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
-    mgmt_fatal(errno, "[getAddrForIntr] Unable to create socket\n");
+    Fatal("[getAddrForIntr] Unable to create socket: %d\n", errno);
   }
   // INKqa06739
   // Fetch the list of network interfaces
@@ -372,7 +272,7 @@
     ifc.ifc_buf = ifbuf;
     if (ioctl(fakeSocket, SIOCGIFCONF, &ifc) < 0) {
       if (errno != EINVAL || lastlen != 0) {
-        mgmt_fatal(errno, "[getAddrForIntr] Unable to read network interface configuration\n");
+        Fatal("[getAddrForIntr] Unable to read network interface configuration: %d\n", errno);
       }
     } else {
       if (ifc.ifc_len == lastlen) {
@@ -391,7 +291,7 @@
     if (ifr->ifr_addr.sa_family == AF_INET && strcmp(ifr->ifr_name, intrName) == 0) {
       // Get the address of the interface
       if (ioctl(fakeSocket, SIOCGIFADDR, reinterpret_cast<char *>(ifr)) < 0) {
-        mgmt_log("[getAddrForIntr] Unable obtain address for network interface %s\n", intrName);
+        Debug("mgmt_utils", "[getAddrForIntr] Unable obtain address for network interface %s\n", intrName);
       } else {
         // Only look at the address if it an internet address
         if (ifr->ifr_ifru.ifru_addr.sa_family == AF_INET) {
@@ -404,7 +304,7 @@
 
           break;
         } else {
-          mgmt_log("[getAddrForIntr] Interface %s is not configured for IP.\n", intrName);
+          Debug("mgmt_utils", "[getAddrForIntr] Interface %s is not configured for IP.\n", intrName);
         }
       }
     }
diff --git a/mgmt/utils/MgmtUtils.h b/mgmt/utils/MgmtUtils.h
index 15721f9..9c658b8 100644
--- a/mgmt/utils/MgmtUtils.h
+++ b/mgmt/utils/MgmtUtils.h
@@ -50,12 +50,6 @@
 struct in_addr *mgmt_sortipaddrs(int num, struct in_addr **list);
 bool mgmt_getAddrForIntr(char *intrName, sockaddr *addr, int *mtu = nullptr);
 
-/* the following functions are all DEPRECATED.  The Diags
-   interface should be used exclusively in the future */
-void mgmt_log(const char *message_format, ...);
-void mgmt_elog(const int lerrno, const char *message_format, ...);
-void mgmt_fatal(const int lerrno, const char *message_format, ...) TS_NORETURN;
-
 void mgmt_sleep_sec(int);
 void mgmt_sleep_msec(int);
 
diff --git a/mgmt/utils/unit_tests/test_marshall.cc b/mgmt/utils/unit_tests/test_marshall.cc
deleted file mode 100644
index b2bef2d..0000000
--- a/mgmt/utils/unit_tests/test_marshall.cc
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * 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 <iostream>
-#include <string_view>
-#include <cstdint>
-
-#include <tscore/ink_defs.h>
-#include <tscore/ink_thread.h>
-#include <tscore/ink_inet.h>
-
-#include <MgmtMarshall.h>
-#include <MgmtSocket.h>
-
-#include <catch.hpp>
-
-namespace
-{
-bool
-check_eq(char const *expr_as_str, MgmtMarshallInt rcvd, MgmtMarshallInt len)
-{
-  if (rcvd == len) {
-    return true;
-  }
-  std::cout << expr_as_str << " returned length " << rcvd << ", expected " << len << '\n';
-  return false;
-}
-
-#define CHECK_EQ(expr, len) CHECK(check_eq(#expr, static_cast<MgmtMarshallInt>(expr), static_cast<MgmtMarshallInt>(len)))
-
-template <typename V_t, typename E_t>
-bool
-check_value(V_t value, E_t expect)
-{
-  if (sizeof(V_t) > sizeof(E_t) ? (value == static_cast<V_t>(expect)) : (static_cast<E_t>(value) == expect)) {
-    return true;
-  }
-  std::cout << "received marshalled value " << value << ", expected " << expect << '\n';
-  return false;
-}
-
-bool
-check_value(void *value, void *expect)
-{
-  if (value == expect) {
-    return true;
-  }
-  std::cout << std::hex << "received marshalled value " << reinterpret_cast<std::uintptr_t>(value) << ", expected "
-            << reinterpret_cast<std::uintptr_t>(expect) << '\n'
-            << std::dec;
-  return false;
-}
-
-#define CHECK_VALUE(value, expect) CHECK(check_value((value), (expect)))
-
-bool
-check_str(char const *value, char const *expect)
-{
-  if (!value) {
-    value = "";
-  }
-  if (std::string_view(value) == expect) {
-    return true;
-  }
-  std::cout << "received marshalled value " << value << ", expected " << expect << '\n';
-  return false;
-}
-
-#define CHECK_STR(value, expect) CHECK(check_str((value), (expect)))
-
-const MgmtMarshallType inval[] = {static_cast<MgmtMarshallType>(1568)};
-
-const MgmtMarshallType ifields[] = {MGMT_MARSHALL_INT, MGMT_MARSHALL_LONG};
-
-const MgmtMarshallType sfields[] = {
-  MGMT_MARSHALL_STRING,
-};
-
-const MgmtMarshallType dfields[] = {
-  MGMT_MARSHALL_DATA,
-};
-
-const MgmtMarshallType afields[] = {
-  MGMT_MARSHALL_DATA, MGMT_MARSHALL_INT, MGMT_MARSHALL_LONG, MGMT_MARSHALL_STRING, MGMT_MARSHALL_LONG, MGMT_MARSHALL_LONG,
-};
-
-const char alpha[]       = "abcdefghijklmnopqrstuvwxyz0123456789";
-const char *stringvals[] = {nullptr, "", "randomstring"};
-
-bool
-errno_is_continue()
-{
-  return errno == EALREADY || errno == EWOULDBLOCK || errno == EINPROGRESS || errno == EAGAIN || mgmt_transient_error();
-}
-
-int
-message_connect_channel(int listenfd, int clientfd, int serverport)
-{
-  //  bool need_connect = true;
-  bool need_accept = true;
-  int serverfd     = -1;
-
-  struct sockaddr_in in;
-
-  ink_zero(in);
-  in.sin_family      = AF_INET;
-  in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-  in.sin_port        = htons(serverport);
-
-  fcntl(clientfd, F_SETFL, O_NONBLOCK);
-  fcntl(listenfd, F_SETFL, O_NONBLOCK);
-
-  connect(clientfd, reinterpret_cast<const struct sockaddr *>(&in), sizeof(in));
-
-  while (need_accept) {
-    serverfd = accept(listenfd, nullptr, nullptr);
-    if (serverfd == -1) {
-      std::cout << "accepting, " << errno << ' ' << strerror(errno) << '\n';
-      if (!errno_is_continue()) {
-        return -1;
-      }
-      ink_thr_yield();
-    } else {
-      need_accept = false;
-    }
-  }
-
-  return serverfd;
-}
-
-int
-message_listen(int &port)
-{
-  IpEndpoint sa;
-  socklen_t slen;
-  int fd;
-
-  fd = mgmt_socket(AF_INET, SOCK_STREAM, 0);
-  if (fd >= 0) {
-    sa.setToAnyAddr(AF_INET);
-    if (bind(fd, &sa.sa, sizeof(sa.sin)) >= 0) {
-      slen = sizeof(sa);
-      if (getsockname(fd, &sa.sa, &slen) >= -1) {
-        port = ntohs(ats_ip_port_cast(&sa.sa));
-
-        listen(fd, 5);
-
-        return fd;
-      }
-    }
-    close(fd);
-  }
-  return -1;
-}
-
-} // end anonymous namespace
-
-TEST_CASE("MessageReadWriteA", "[mgmt_utils][msg_rd_wr_a]")
-{
-  int listenfd   = -1;
-  int serverfd   = -1;
-  int clientfd   = -1;
-  int serverport = 0;
-
-  MgmtMarshallInt mint       = 0;
-  MgmtMarshallLong mlong     = 0;
-  MgmtMarshallString mstring = nullptr;
-  MgmtMarshallData mdata     = {nullptr, 0};
-
-  clientfd = mgmt_socket(AF_INET, SOCK_STREAM, 0);
-  listenfd = message_listen(serverport);
-  serverfd = message_connect_channel(listenfd, clientfd, serverport);
-  std::cout << "listenfd=" << listenfd << " clientfd=" << clientfd << " serverfd=" << serverfd << " port=" << serverport << '\n';
-
-  fcntl(clientfd, F_SETFL, O_NDELAY);
-  fcntl(serverfd, F_SETFL, O_NDELAY);
-
-  mint  = 99;
-  mlong = reinterpret_cast<MgmtMarshallLong>(&listenfd);
-
-  // Check invalid Fd write. ToDo: Commented out, see TS-3052.
-  // CHECK_EQ(mgmt_message_write(FD_SETSIZE - 1, ifields, countof(ifields), &mint, &mlong), -1);
-
-  CHECK_EQ(mgmt_message_write(clientfd, ifields, countof(ifields), &mint, &mlong), 12);
-
-  mint  = 0;
-  mlong = 0;
-  CHECK_EQ(mgmt_message_read(serverfd, ifields, countof(ifields), &mint, &mlong), 12);
-  CHECK_VALUE(mint, 99);
-  CHECK_VALUE(mlong, reinterpret_cast<MgmtMarshallLong>(&listenfd));
-
-  // Marshall a string.
-  for (unsigned i = 0; i < countof(stringvals); ++i) {
-    const char *s   = stringvals[i];
-    std::size_t len = 4 /* length */ + (s ? std::strlen(s) : 0) /* bytes */ + 1 /* NULL */;
-
-    mstring = s ? ats_strdup(s) : nullptr;
-    CHECK_EQ(mgmt_message_write(clientfd, sfields, countof(sfields), &mstring), len);
-    ats_free(mstring);
-    mstring = nullptr;
-
-    CHECK_EQ(mgmt_message_read(serverfd, sfields, countof(sfields), &mstring), len);
-    CHECK_STR(s, mstring);
-    ats_free(mstring);
-    mstring = nullptr;
-  }
-
-  // Marshall data.
-  mdata.ptr = ats_strdup(alpha);
-  mdata.len = std::strlen(alpha);
-  CHECK_EQ(mgmt_message_write(clientfd, dfields, countof(dfields), &mdata), 4 + std::strlen(alpha));
-  ats_free(mdata.ptr);
-  ink_zero(mdata);
-
-  CHECK_EQ(mgmt_message_read(serverfd, dfields, countof(dfields), &mdata), 4 + std::strlen(alpha));
-  CHECK_VALUE(mdata.len, std::strlen(alpha));
-  if (std::memcmp(mdata.ptr, alpha, std::strlen(alpha)) != 0) {
-    std::cout << "unexpected mdata contents\n";
-    CHECK(false);
-  }
-  ats_free(mdata.ptr);
-  ink_zero(mdata);
-
-  close(clientfd);
-  close(listenfd);
-  close(serverfd);
-}
-
-TEST_CASE("MessageMarshall", "[mgmt_utils][msg_marshall]")
-{
-  char msgbuf[4096];
-
-  MgmtMarshallInt mint       = 0;
-  MgmtMarshallLong mlong     = 0;
-  MgmtMarshallString mstring = nullptr;
-  MgmtMarshallData mdata     = {nullptr, 0};
-
-  // Parse empty message.
-  CHECK_EQ(mgmt_message_parse(nullptr, 0, nullptr, 0), 0);
-
-  // Marshall empty message.
-  CHECK_EQ(mgmt_message_marshall(nullptr, 0, nullptr, 0), 0);
-
-  // Marshall some integral types.
-  mint  = -156;
-  mlong = UINT32_MAX;
-  CHECK_EQ(mgmt_message_marshall(msgbuf, 1, ifields, countof(ifields), &mint, &mlong), -1);
-  CHECK_EQ(mgmt_message_marshall(msgbuf, sizeof(msgbuf), ifields, countof(ifields), &mint, &mlong), 12);
-  CHECK_EQ(mgmt_message_parse(msgbuf, 1, ifields, countof(ifields), &mint, &mlong), -1);
-  CHECK_EQ(mgmt_message_parse(msgbuf, sizeof(msgbuf), ifields, countof(ifields), &mint, &mlong), 12);
-  CHECK_VALUE(mint, -156);
-  CHECK_VALUE(mlong, static_cast<MgmtMarshallLong>(UINT32_MAX));
-
-  // Marshall a string.
-  for (unsigned i = 0; i < countof(stringvals); ++i) {
-    const char *s = stringvals[i];
-    size_t len    = 4 /* length */ + (s ? std::strlen(s) : 0) /* bytes */ + 1 /* NULL */;
-
-    mstring = s ? ats_strdup(s) : nullptr;
-    CHECK_EQ(mgmt_message_marshall(msgbuf, 1, sfields, countof(sfields), &mstring), -1);
-    CHECK_EQ(mgmt_message_marshall(msgbuf, sizeof(msgbuf), sfields, countof(sfields), &mstring), len);
-    ats_free(mstring);
-    mstring = nullptr;
-
-    CHECK_EQ(mgmt_message_parse(msgbuf, 1, sfields, countof(sfields), &mstring), -1);
-    CHECK_EQ(mgmt_message_parse(msgbuf, sizeof(msgbuf), sfields, countof(sfields), &mstring), len);
-    CHECK_STR(s, mstring);
-    ats_free(mstring);
-    mstring = nullptr;
-  }
-
-  // Marshall data.
-  mdata.ptr = ats_strdup(alpha);
-  mdata.len = std::strlen(alpha);
-  CHECK_EQ(mgmt_message_marshall(msgbuf, 10, dfields, countof(dfields), &mdata), -1);
-  CHECK_EQ(mgmt_message_marshall(msgbuf, sizeof(msgbuf), dfields, countof(dfields), &mdata), 4 + std::strlen(alpha));
-  ats_free(mdata.ptr);
-  ink_zero(mdata);
-
-  CHECK_EQ(mgmt_message_parse(msgbuf, std::strlen(alpha), dfields, countof(dfields), &mdata), -1);
-  CHECK_EQ(mgmt_message_parse(msgbuf, std::strlen(alpha) + 4, dfields, countof(dfields), &mdata), 4 + std::strlen(alpha));
-  CHECK_VALUE(mdata.len, std::strlen(alpha));
-  if (std::memcmp(mdata.ptr, alpha, std::strlen(alpha)) != 0) {
-    std::cout << "unexpected mdata contents\n";
-    CHECK(false);
-  }
-  ats_free(mdata.ptr);
-  ink_zero(mdata);
-
-  // Marshall empty data.
-  CHECK_EQ(mgmt_message_marshall(msgbuf, sizeof(msgbuf), dfields, countof(dfields), &mdata), 4);
-
-  mdata.ptr = reinterpret_cast<void *>(99);
-  mdata.len = 1000;
-  CHECK_EQ(mgmt_message_parse(msgbuf, sizeof(msgbuf), dfields, countof(dfields), &mdata), 4);
-  CHECK_VALUE(mdata.ptr, static_cast<void *>(nullptr));
-  CHECK_VALUE(mdata.len, 0);
-}
-
-TEST_CASE("MessageLemgth", "[mgmt_utils][msg_len]")
-{
-  MgmtMarshallInt mint       = 0;
-  MgmtMarshallLong mlong     = 0;
-  MgmtMarshallString mstring = nullptr;
-  MgmtMarshallData mdata     = {nullptr, 0};
-
-  // Check invalid marshall type.
-  CHECK_EQ(mgmt_message_length(inval, countof(inval), NULL), -1);
-
-  // Check empty types array.
-  CHECK_EQ(mgmt_message_length(nullptr, 0), 0);
-
-  CHECK_EQ(mgmt_message_length(ifields, countof(ifields), &mint, &mlong), 12);
-
-  // string messages include a 4-byte length and the NULL
-  mstring = const_cast<char *>("foo");
-  CHECK_EQ(mgmt_message_length(sfields, countof(sfields), &mstring), sizeof("foo") + 4);
-
-  // NULL strings are the same as empty strings ...
-  mstring = nullptr;
-  CHECK_EQ(mgmt_message_length(sfields, countof(sfields), &mstring), 4 + 1);
-  mstring = const_cast<char *>("");
-  CHECK_EQ(mgmt_message_length(sfields, countof(sfields), &mstring), 4 + 1);
-
-  // data fields include a 4-byte length. We don't go looking at the data in this case.
-  mdata.len = 99;
-  mdata.ptr = nullptr;
-  CHECK_EQ(mgmt_message_length(dfields, countof(dfields), &mdata), 99 + 4);
-
-  mstring   = (char *)"all fields";
-  mdata.len = 31;
-  CHECK_EQ(mgmt_message_length(afields, countof(afields), &mdata, &mint, &mlong, &mstring, &mlong, &mlong),
-           31 + 4 + 4 + 8 + sizeof("all fields") + 4 + 8 + 8);
-
-  mdata.ptr = nullptr;
-  mdata.len = 0;
-  CHECK_EQ(mgmt_message_length(dfields, countof(dfields), &mdata), 4);
-}
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index adb3d3b..e8c7ec7 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -129,10 +129,6 @@
 include experimental/magick/Makefile.inc
 endif
 
-if HAS_MYSQL
-include experimental/mysql_remap/Makefile.inc
-endif
-
 endif
 
 TESTS_ENVIRONMENT = LSAN_OPTIONS=suppressions=$(top_srcdir)/plugins/suppression.txt
diff --git a/plugins/esi/esi.cc b/plugins/esi/esi.cc
index 3eb18d2..e1ec1c4 100644
--- a/plugins/esi/esi.cc
+++ b/plugins/esi/esi.cc
@@ -578,7 +578,8 @@
 static void
 cacheNodeList(ContData *cont_data)
 {
-  if (TSHttpTxnAborted(cont_data->txnp) == TS_SUCCESS) {
+  bool client_abort;
+  if (TSHttpTxnAborted(cont_data->txnp, &client_abort) == TS_SUCCESS) {
     TSDebug(cont_data->debug_tag, "[%s] Not caching node list as txn has been aborted", __FUNCTION__);
     return;
   }
diff --git a/plugins/experimental/maxmind_acl/mmdb.cc b/plugins/experimental/maxmind_acl/mmdb.cc
index 0fd1bb0..7157e1d 100644
--- a/plugins/experimental/maxmind_acl/mmdb.cc
+++ b/plugins/experimental/maxmind_acl/mmdb.cc
@@ -242,10 +242,10 @@
         if (ip.IsSequence()) {
           // Do IP Deny processing
           for (auto &&i : ip) {
-            IpAddr min, max;
-            ats_ip_range_parse(std::string_view{i.as<std::string>()}, min, max);
-            deny_ip_map.fill(min, max, nullptr);
-            TSDebug(PLUGIN_NAME, "loading ip: valid: %d, fam %d ", min.isValid(), min.family());
+            if (swoc::IPRange r; r.load(i.Scalar())) {
+              deny_ip_map.fill(r);
+              TSDebug(PLUGIN_NAME, "Denying ip fam %d ", r.family());
+            }
           }
         } else {
           TSDebug(PLUGIN_NAME, "Invalid IP deny list yaml");
@@ -323,10 +323,10 @@
         if (ip.IsSequence()) {
           // Do IP Allow processing
           for (auto &&i : ip) {
-            IpAddr min, max;
-            ats_ip_range_parse(std::string_view{i.as<std::string>()}, min, max);
-            allow_ip_map.fill(min, max, nullptr);
-            TSDebug(PLUGIN_NAME, "loading ip: valid: %d, fam %d ", min.isValid(), min.family());
+            if (swoc::IPRange r; r.load(i.Scalar())) {
+              allow_ip_map.fill(r);
+              TSDebug(PLUGIN_NAME, "loading ip: valid: fam %d ", r.family());
+            }
           }
         } else {
           TSDebug(PLUGIN_NAME, "Invalid IP allow list yaml");
@@ -749,12 +749,13 @@
   }
 #endif
 
-  if (allow_ip_map.contains(sock, nullptr)) {
+  swoc::IPAddr addr(sock);
+  if (allow_ip_map.contains(addr)) {
     // Allow map has this ip, we know we want to allow it
     return ALLOW_IP;
   }
 
-  if (deny_ip_map.contains(sock, nullptr)) {
+  if (deny_ip_map.contains(addr)) {
     // Deny map has this ip, explicitly deny
     return DENY_IP;
   }
diff --git a/plugins/experimental/maxmind_acl/mmdb.h b/plugins/experimental/maxmind_acl/mmdb.h
index 2bdb9df..372cfda 100644
--- a/plugins/experimental/maxmind_acl/mmdb.h
+++ b/plugins/experimental/maxmind_acl/mmdb.h
@@ -36,7 +36,7 @@
 #include <unistd.h>
 #include <iterator>
 #include <maxminddb.h>
-#include "tscore/IpMap.h"
+#include "tscpp/util/ts_ip.h"
 
 #ifdef HAVE_PCRE_PCRE_H
 #include <pcre/pcre.h>
@@ -91,8 +91,8 @@
   std::unordered_map<std::string, std::vector<plugin_regex>> allow_regex;
   std::unordered_map<std::string, std::vector<plugin_regex>> deny_regex;
 
-  IpMap allow_ip_map;
-  IpMap deny_ip_map;
+  ts::IPAddrSet allow_ip_map;
+  ts::IPAddrSet deny_ip_map;
 
   // Anonymous blocking default to off
   bool _anonymous_ip      = false;
diff --git a/plugins/experimental/memcache/tsmemcache.cc b/plugins/experimental/memcache/tsmemcache.cc
index 9443c97..cb1b32f 100644
--- a/plugins/experimental/memcache/tsmemcache.cc
+++ b/plugins/experimental/memcache/tsmemcache.cc
@@ -24,6 +24,7 @@
 #include "tsmemcache.h"
 #include "I_NetVConnection.h"
 #include "I_NetProcessor.h"
+#include "tscore/ink_atomic.h"
 
 /*
   TODO
@@ -43,7 +44,6 @@
 // These should be persistent.
 int32_t MC::verbosity     = 0;
 ink_hrtime MC::last_flush = 0;
-int64_t MC::next_cas      = 1;
 
 static void
 tsmemcache_constants()
@@ -787,7 +787,7 @@
         return ASCII_RESPONSE("EXISTS");
       }
     }
-    header.cas = ink_atomic_increment(&next_cas, 1);
+    header.cas = next_cas++;
     if (f.set_append || f.set_prepend) {
       header.nbytes = nbytes + rcache_header->nbytes;
     } else {
@@ -934,7 +934,7 @@
         header.exptime = UINT32_MAX; // 136 years
       }
     }
-    header.cas = ink_atomic_increment(&next_cas, 1);
+    header.cas = next_cas++;
     {
       char *localdata = nullptr;
       int len         = 0;
@@ -1365,7 +1365,7 @@
 #if __WORDSIZE == 64
     last_flush = new_last_flush; // this will be atomic for native word size
 #else
-    ink_atomic_swap(&last_flush, new_last_flush);
+    last_flush.exchange(new_last_flush);
 #endif
     if (!is_end_of_cmd(s, e)) {
       break;
diff --git a/plugins/experimental/memcache/tsmemcache.h b/plugins/experimental/memcache/tsmemcache.h
index 7b0d5c3..69a7881 100644
--- a/plugins/experimental/memcache/tsmemcache.h
+++ b/plugins/experimental/memcache/tsmemcache.h
@@ -21,6 +21,8 @@
   limitations under the License.
  */
 
+#include <atomic>
+
 #include "I_EventSystem.h"
 #include "I_Net.h"
 #include "I_Cache.h"
@@ -154,8 +156,12 @@
   uint64_t delta;
 
   static int32_t verbosity;
+#if __WORDSIZE == 64
   static ink_hrtime last_flush;
-  static int64_t next_cas;
+#else
+  static std::atomic<ink_hrtime> last_flush;
+#endif
+  static inline std::atomic<int64_t> next_cas = 1;
 
   int write_to_client(int64_t ntowrite = -1);
   int write_then_read_from_client(int64_t ntowrite = -1);
diff --git a/plugins/experimental/mysql_remap/AUTHORS b/plugins/experimental/mysql_remap/AUTHORS
deleted file mode 100644
index 3c25016..0000000
--- a/plugins/experimental/mysql_remap/AUTHORS
+++ /dev/null
@@ -1,7 +0,0 @@
-Author: Eric Balsa <ericb@apache.org>
-Original concept and code
-
-====
-
-Author: Nicolas Devillard
-INI parsing library
diff --git a/plugins/experimental/mysql_remap/Makefile.inc b/plugins/experimental/mysql_remap/Makefile.inc
deleted file mode 100644
index f15cead..0000000
--- a/plugins/experimental/mysql_remap/Makefile.inc
+++ /dev/null
@@ -1,29 +0,0 @@
-#  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.
-
-pkglib_LTLIBRARIES += experimental/mysql_remap/mysql_remap.la
-
-experimental_mysql_remap_mysql_remap_la_SOURCES = \
-	experimental/mysql_remap/mysql_remap.cc \
-	experimental/mysql_remap/lib/dictionary.c  \
-	experimental/mysql_remap/lib/dictionary.h  \
-	experimental/mysql_remap/lib/iniparser.c   \
-	experimental/mysql_remap/lib/iniparser.h
-
-experimental_mysql_remap_mysql_remap_la_LDFLAGS = \
-	$(AM_LDFLAGS) $(LIB_MYSQLCLIENT)
-
-# vim: ft=make ts=8 sw=8 et:
diff --git a/plugins/experimental/mysql_remap/README b/plugins/experimental/mysql_remap/README
deleted file mode 100644
index 2817fe1..0000000
--- a/plugins/experimental/mysql_remap/README
+++ /dev/null
@@ -1,74 +0,0 @@
-Apache Traffic Server MySQL-based remap plugin
-==============================================
-
-== Description ==
-  This is a basic plugin for doing dynamic "remaps" from a database. It essentially rewrites the incoming request's Host header / origin server connection
-to one retrieved from a database.
-
-The generic proxying setup is the following:
-
-UA ----> Traffic Server ----> Origin Server
-
-Without the plugin a request like:
-
-GET /path/to/something HTTP/1.1
-Host: original.host.com
-
-Ends up requesting http://original.host.com/path/to/something
-
-With this plugin enabled, you can easily change that to anywhere you desire. Imagine the many possibilities....
-
-I have benchmarked this at about 9200 requests/sec (1.7k object) on a junky imac with mysql, ab & trafficserver local. Real performance
-is likely substantially higher up to your mysql's max queries / second.
-
-== Build ==
-  A simple
-
-% make install
-
-should do it, assuming that you have the tsxs script in your search path.
-This script is installed with your installation of Apache Traffic Server.
-
-NOTE: you may need to open the Makefile and adjust the paths to MySQL client includes & libraries
-
-== Configuration ==
-Import the default schema to a database you create:
-
-mysql -u root -p -e "CREATE DATABASE mysql_remap;"   # create a new database
-mysql -u root -p mysql_remap < schema/import.sql     # import the provided schema
-
-insert some interesting values in mysql_remap.hostname & mysql_remap.map
-
-Traffic Server plugin configuration is done inside a global configuration file: /path/to/etc/trafficserver/plugin.config:
-
-  mysql_remap.so /path/to/sample.ini
-
-The INI file should contain the following values:
-
-[mysql_remap]
-mysql_host     = localhost   #default
-mysql_port     = 3306        #default
-mysql_username = root
-mysql_password =
-mysql_database = mysql_remap #default
-
-To debug errors, start trafficserver manually using:
-
-/path/to/traffic_server -T "mysql_remap"
-
-And resolve any errors or warnings displayed.
-
-== Credits ==
-  * Eric Balsa
-  * ericb@apache.org / eric@ericbalsa.com
-  * http://www.ericbalsa.com
-
-== TODO ==
-  * some stupid bug in the ini parsing requiring a blank trailing \n
-  * make db backend pluggable
-  * handle mysql connections a bit more intelligently
-  * define a fallback host for missing remaps (instead of blindly issuing a 404)
-  * handle rewriting paths
-  * handle regexp in paths & hosts
-  * verify scheme switching
-  * ... many more ideas ...
diff --git a/plugins/experimental/mysql_remap/lib/dictionary.c b/plugins/experimental/mysql_remap/lib/dictionary.c
deleted file mode 100644
index 4cc1d0f..0000000
--- a/plugins/experimental/mysql_remap/lib/dictionary.c
+++ /dev/null
@@ -1,450 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*-------------------------------------------------------------------------*/
-/**
-   @file	dictionary.c
-   @author	N. Devillard
-   @date	Sep 2007
-   @version	$Revision: 1.27 $
-   @brief	Implements a dictionary for string variables.
-
-   This module implements a simple dictionary object, i.e. a list
-   of string/string associations. This object is useful to store e.g.
-   information retrieved from a configuration file (ini files).
-*/
-/*--------------------------------------------------------------------------*/
-
-/*
-        $Id: dictionary.c,v 1.27 2007-11-23 21:39:18 ndevilla Exp $
-        $Revision: 1.27 $
-*/
-/*---------------------------------------------------------------------------
-                                                                Includes
- ---------------------------------------------------------------------------*/
-#include "dictionary.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-/** Maximum value size for integers and doubles. */
-#define MAXVALSZ 1024
-
-/** Minimal allocated number of entries in a dictionary */
-#define DICTMINSZ 128
-
-/** Invalid key token */
-#define DICT_INVALID_KEY ((char *)-1)
-
-/*---------------------------------------------------------------------------
-                                                        Private functions
- ---------------------------------------------------------------------------*/
-
-/* Doubles the allocated size associated to a pointer */
-/* 'size' is the current allocated size. */
-static void *
-mem_double(void *ptr, int size)
-{
-  void *newptr;
-
-  newptr = calloc(2 * size, 1);
-  if (newptr == NULL) {
-    return NULL;
-  }
-  memcpy(newptr, ptr, size);
-  free(ptr);
-  return newptr;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Duplicate a string
-  @param    s String to duplicate
-  @return   Pointer to a newly allocated string, to be freed with free()
-
-  This is a replacement for strdup(). This implementation is provided
-  for systems that do not have it.
- */
-/*--------------------------------------------------------------------------*/
-static char *
-xstrdup(char *s)
-{
-  char *t;
-  if (!s) {
-    return NULL;
-  }
-  t = (char *)malloc(strlen(s) + 1);
-  if (t) {
-    strcpy(t, s);
-  }
-  return t;
-}
-
-/*---------------------------------------------------------------------------
-                                                        Function codes
- ---------------------------------------------------------------------------*/
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Compute the hash key for a string.
-  @param	key		Character string to use for key.
-  @return	1 unsigned int on at least 32 bits.
-
-  This hash function has been taken from an Article in Dr Dobbs Journal.
-  This is normally a collision-free function, distributing keys evenly.
-  The key is stored anyway in the struct so that collision can be avoided
-  by comparing the key itself in last resort.
- */
-/*--------------------------------------------------------------------------*/
-unsigned
-dictionary_hash(char *key)
-{
-  int len;
-  unsigned hash;
-  int i;
-
-  len = strlen(key);
-  for (hash = 0, i = 0; i < len; i++) {
-    hash += (unsigned)key[i];
-    hash += (hash << 10);
-    hash ^= (hash >> 6);
-  }
-  hash += (hash << 3);
-  hash ^= (hash >> 11);
-  hash += (hash << 15);
-  return hash;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Create a new dictionary object.
-  @param	size	Optional initial size of the dictionary.
-  @return	1 newly allocated dictionary object.
-
-  This function allocates a new dictionary object of given size and returns
-  it. If you do not know in advance (roughly) the number of entries in the
-  dictionary, give size=0.
- */
-/*--------------------------------------------------------------------------*/
-dictionary *
-dictionary_new(int size)
-{
-  dictionary *d;
-
-  /* If no size was specified, allocate space for DICTMINSZ */
-  if (size < DICTMINSZ) {
-    size = DICTMINSZ;
-  }
-
-  if (!(d = (dictionary *)calloc(1, sizeof(dictionary)))) {
-    return NULL;
-  }
-  d->size = size;
-  d->val  = (char **)calloc(size, sizeof(char *));
-  d->key  = (char **)calloc(size, sizeof(char *));
-  d->hash = (unsigned int *)calloc(size, sizeof(unsigned));
-  return d;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Delete a dictionary object
-  @param	d	dictionary object to deallocate.
-  @return	void
-
-  Deallocate a dictionary object and all memory associated to it.
- */
-/*--------------------------------------------------------------------------*/
-void
-dictionary_del(dictionary *d)
-{
-  int i;
-
-  if (d == NULL) {
-    return;
-  }
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] != NULL) {
-      free(d->key[i]);
-    }
-    if (d->val[i] != NULL) {
-      free(d->val[i]);
-    }
-  }
-  free(d->val);
-  free(d->key);
-  free(d->hash);
-  free(d);
-  return;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Get a value from a dictionary.
-  @param	d		dictionary object to search.
-  @param	key		Key to look for in the dictionary.
-  @param    def     Default value to return if key not found.
-  @return	1 pointer to internally allocated character string.
-
-  This function locates a key in a dictionary and returns a pointer to its
-  value, or the passed 'def' pointer if no such key can be found in
-  dictionary. The returned character pointer points to data internal to the
-  dictionary object, you should not try to free it or modify it.
- */
-/*--------------------------------------------------------------------------*/
-char *
-dictionary_get(dictionary *d, char *key, char *def)
-{
-  unsigned hash;
-  int i;
-
-  hash = dictionary_hash(key);
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] == NULL) {
-      continue;
-    }
-    /* Compare hash */
-    if (hash == d->hash[i]) {
-      /* Compare string, to avoid hash collisions */
-      if (!strcmp(key, d->key[i])) {
-        return d->val[i];
-      }
-    }
-  }
-  return def;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Set a value in a dictionary.
-  @param    d       dictionary object to modify.
-  @param    key     Key to modify or add.
-  @param    val     Value to add.
-  @return   int     0 if Ok, anything else otherwise
-
-  If the given key is found in the dictionary, the associated value is
-  replaced by the provided one. If the key cannot be found in the
-  dictionary, it is added to it.
-
-  It is Ok to provide a NULL value for val, but NULL values for the dictionary
-  or the key are considered as errors: the function will return immediately
-  in such a case.
-
-  Notice that if you dictionary_set a variable to NULL, a call to
-  dictionary_get will return a NULL value: the variable will be found, and
-  its value (NULL) is returned. In other words, setting the variable
-  content to NULL is equivalent to deleting the variable from the
-  dictionary. It is not possible (in this implementation) to have a key in
-  the dictionary without value.
-
-  This function returns non-zero in case of failure.
- */
-/*--------------------------------------------------------------------------*/
-int
-dictionary_set(dictionary *d, char *key, char *val)
-{
-  int i;
-  unsigned hash;
-
-  if (d == NULL || key == NULL) {
-    return -1;
-  }
-
-  /* Compute hash for this key */
-  hash = dictionary_hash(key);
-  /* Find if value is already in dictionary */
-  if (d->n > 0) {
-    for (i = 0; i < d->size; i++) {
-      if (d->key[i] == NULL) {
-        continue;
-      }
-      if (hash == d->hash[i]) {        /* Same hash value */
-        if (!strcmp(key, d->key[i])) { /* Same key */
-          /* Found a value: modify and return */
-          if (d->val[i] != NULL) {
-            free(d->val[i]);
-          }
-          d->val[i] = val ? xstrdup(val) : NULL;
-          /* Value has been modified: return */
-          return 0;
-        }
-      }
-    }
-  }
-  /* Add a new value */
-  /* See if dictionary needs to grow */
-  if (d->n == d->size) {
-    /* Reached maximum size: reallocate dictionary */
-    d->val  = (char **)mem_double(d->val, d->size * sizeof(char *));
-    d->key  = (char **)mem_double(d->key, d->size * sizeof(char *));
-    d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned));
-    if ((d->val == NULL) || (d->key == NULL) || (d->hash == NULL)) {
-      /* Cannot grow dictionary */
-      return -1;
-    }
-    /* Double size */
-    d->size *= 2;
-  }
-
-  /* Insert key in the first empty slot */
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] == NULL) {
-      /* Add key here */
-      break;
-    }
-  }
-  /* Copy key */
-  d->key[i]  = xstrdup(key);
-  d->val[i]  = val ? xstrdup(val) : NULL;
-  d->hash[i] = hash;
-  d->n++;
-  return 0;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Delete a key in a dictionary
-  @param	d		dictionary object to modify.
-  @param	key		Key to remove.
-  @return   void
-
-  This function deletes a key in a dictionary. Nothing is done if the
-  key cannot be found.
- */
-/*--------------------------------------------------------------------------*/
-void
-dictionary_unset(dictionary *d, char *key)
-{
-  unsigned hash;
-  int i;
-
-  if (key == NULL) {
-    return;
-  }
-
-  hash = dictionary_hash(key);
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] == NULL) {
-      continue;
-    }
-    /* Compare hash */
-    if (hash == d->hash[i]) {
-      /* Compare string, to avoid hash collisions */
-      if (!strcmp(key, d->key[i])) {
-        /* Found key */
-        break;
-      }
-    }
-  }
-  if (i >= d->size) {
-    /* Key not found */
-    return;
-  }
-
-  free(d->key[i]);
-  d->key[i] = NULL;
-  if (d->val[i] != NULL) {
-    free(d->val[i]);
-    d->val[i] = NULL;
-  }
-  d->hash[i] = 0;
-  d->n--;
-  return;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Dump a dictionary to an opened file pointer.
-  @param	d	Dictionary to dump
-  @param	f	Opened file pointer.
-  @return	void
-
-  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
-  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
-  output file pointers.
- */
-/*--------------------------------------------------------------------------*/
-void
-dictionary_dump(dictionary *d, FILE *out)
-{
-  int i;
-
-  if (d == NULL || out == NULL) {
-    return;
-  }
-  if (d->n < 1) {
-    fprintf(out, "empty dictionary\n");
-    return;
-  }
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i]) {
-      fprintf(out, "%20s\t[%s]\n", d->key[i], d->val[i] ? d->val[i] : "UNDEF");
-    }
-  }
-  return;
-}
-
-/* Test code */
-#ifdef TESTDIC
-#define NVALS 20000
-int
-main(int argc, char *argv[])
-{
-  dictionary *d;
-  char *val;
-  int i;
-  char cval[90];
-
-  /* Allocate dictionary */
-  printf("allocating...\n");
-  d = dictionary_new(0);
-
-  /* Set values in dictionary */
-  printf("setting %d values...\n", NVALS);
-  for (i = 0; i < NVALS; i++) {
-    sprintf(cval, "%04d", i);
-    dictionary_set(d, cval, "salut");
-  }
-  printf("getting %d values...\n", NVALS);
-  for (i = 0; i < NVALS; i++) {
-    sprintf(cval, "%04d", i);
-    val = dictionary_get(d, cval, DICT_INVALID_KEY);
-    if (val == DICT_INVALID_KEY) {
-      printf("cannot get value for key [%s]\n", cval);
-    }
-  }
-  printf("unsetting %d values...\n", NVALS);
-  for (i = 0; i < NVALS; i++) {
-    sprintf(cval, "%04d", i);
-    dictionary_unset(d, cval);
-  }
-  if (d->n != 0) {
-    printf("error deleting values\n");
-  }
-  printf("deallocating...\n");
-  dictionary_del(d);
-  return 0;
-}
-#endif
-/* vim: set ts=4 et sw=4 tw=75 */
diff --git a/plugins/experimental/mysql_remap/lib/dictionary.h b/plugins/experimental/mysql_remap/lib/dictionary.h
deleted file mode 100644
index 4acdcec..0000000
--- a/plugins/experimental/mysql_remap/lib/dictionary.h
+++ /dev/null
@@ -1,189 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*-------------------------------------------------------------------------*/
-/**
-   @file    dictionary.h
-   @author  N. Devillard
-   @date    Sep 2007
-   @version $Revision: 1.12 $
-   @brief   Implements a dictionary for string variables.
-
-   This module implements a simple dictionary object, i.e. a list
-   of string/string associations. This object is useful to store e.g.
-   information retrieved from a configuration file (ini files).
-*/
-/*--------------------------------------------------------------------------*/
-
-/*
-        $Id: dictionary.h,v 1.12 2007-11-23 21:37:00 ndevilla Exp $
-        $Author: ndevilla $
-        $Date: 2007-11-23 21:37:00 $
-        $Revision: 1.12 $
-*/
-
-#pragma once
-
-/*---------------------------------------------------------------------------
-                                                                Includes
- ---------------------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-/*---------------------------------------------------------------------------
-                                                                New types
- ---------------------------------------------------------------------------*/
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Dictionary object
-
-  This object contains a list of string/string associations. Each
-  association is identified by a unique string key. Looking up values
-  in the dictionary is speeded up by the use of a (hopefully collision-free)
-  hash function.
- */
-/*-------------------------------------------------------------------------*/
-typedef struct _dictionary_ {
-  int n;          /** Number of entries in dictionary */
-  int size;       /** Storage size */
-  char **val;     /** List of string values */
-  char **key;     /** List of string keys */
-  unsigned *hash; /** List of hash values for keys */
-} dictionary;
-
-/*---------------------------------------------------------------------------
-                                                        Function prototypes
- ---------------------------------------------------------------------------*/
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Compute the hash key for a string.
-  @param    key     Character string to use for key.
-  @return   1 unsigned int on at least 32 bits.
-
-  This hash function has been taken from an Article in Dr Dobbs Journal.
-  This is normally a collision-free function, distributing keys evenly.
-  The key is stored anyway in the struct so that collision can be avoided
-  by comparing the key itself in last resort.
- */
-/*--------------------------------------------------------------------------*/
-unsigned dictionary_hash(char *key);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Create a new dictionary object.
-  @param    size    Optional initial size of the dictionary.
-  @return   1 newly allocated dictionary object.
-
-  This function allocates a new dictionary object of given size and returns
-  it. If you do not know in advance (roughly) the number of entries in the
-  dictionary, give size=0.
- */
-/*--------------------------------------------------------------------------*/
-dictionary *dictionary_new(int size);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Delete a dictionary object
-  @param    d   dictionary object to deallocate.
-  @return   void
-
-  Deallocate a dictionary object and all memory associated to it.
- */
-/*--------------------------------------------------------------------------*/
-void dictionary_del(dictionary *vd);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get a value from a dictionary.
-  @param    d       dictionary object to search.
-  @param    key     Key to look for in the dictionary.
-  @param    def     Default value to return if key not found.
-  @return   1 pointer to internally allocated character string.
-
-  This function locates a key in a dictionary and returns a pointer to its
-  value, or the passed 'def' pointer if no such key can be found in
-  dictionary. The returned character pointer points to data internal to the
-  dictionary object, you should not try to free it or modify it.
- */
-/*--------------------------------------------------------------------------*/
-char *dictionary_get(dictionary *d, char *key, char *def);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Set a value in a dictionary.
-  @param    d       dictionary object to modify.
-  @param    key     Key to modify or add.
-  @param    val     Value to add.
-  @return   int     0 if Ok, anything else otherwise
-
-  If the given key is found in the dictionary, the associated value is
-  replaced by the provided one. If the key cannot be found in the
-  dictionary, it is added to it.
-
-  It is Ok to provide a NULL value for val, but NULL values for the dictionary
-  or the key are considered as errors: the function will return immediately
-  in such a case.
-
-  Notice that if you dictionary_set a variable to NULL, a call to
-  dictionary_get will return a NULL value: the variable will be found, and
-  its value (NULL) is returned. In other words, setting the variable
-  content to NULL is equivalent to deleting the variable from the
-  dictionary. It is not possible (in this implementation) to have a key in
-  the dictionary without value.
-
-  This function returns non-zero in case of failure.
- */
-/*--------------------------------------------------------------------------*/
-int dictionary_set(dictionary *vd, char *key, char *val);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Delete a key in a dictionary
-  @param    d       dictionary object to modify.
-  @param    key     Key to remove.
-  @return   void
-
-  This function deletes a key in a dictionary. Nothing is done if the
-  key cannot be found.
- */
-/*--------------------------------------------------------------------------*/
-void dictionary_unset(dictionary *d, char *key);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Dump a dictionary to an opened file pointer.
-  @param    d   Dictionary to dump
-  @param    f   Opened file pointer.
-  @return   void
-
-  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
-  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
-  output file pointers.
- */
-/*--------------------------------------------------------------------------*/
-void dictionary_dump(dictionary *d, FILE *out);
diff --git a/plugins/experimental/mysql_remap/lib/iniparser.c b/plugins/experimental/mysql_remap/lib/iniparser.c
deleted file mode 100644
index a06b6d0..0000000
--- a/plugins/experimental/mysql_remap/lib/iniparser.c
+++ /dev/null
@@ -1,694 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*-------------------------------------------------------------------------*/
-/**
-   @file    iniparser.c
-   @author  N. Devillard
-   @date    Sep 2007
-   @version 3.0
-   @brief   Parser for ini files.
-*/
-/*--------------------------------------------------------------------------*/
-/*
-    $Id: iniparser.c,v 2.18 2008-01-03 18:35:39 ndevilla Exp $
-    $Revision: 2.18 $
-    $Date: 2008-01-03 18:35:39 $
-*/
-/*---------------------------- Includes ------------------------------------*/
-#include <ctype.h>
-#include "iniparser.h"
-
-/*---------------------------- Defines -------------------------------------*/
-#define ASCIILINESZ (1024)
-#define INI_INVALID_KEY ((char *)-1)
-
-/*---------------------------------------------------------------------------
-                        Private to this module
- ---------------------------------------------------------------------------*/
-/**
- * This enum stores the status for each parsed line (internal use only).
- */
-typedef enum _line_status_ {
-  LINE_UNPROCESSED,
-  LINE_ERROR,
-  LINE_EMPTY,
-  LINE_COMMENT,
-  LINE_SECTION,
-  LINE_VALUE,
-} line_status;
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Convert a string to lowercase.
-  @param	s	String to convert.
-  @return	ptr to statically allocated string.
-
-  This function returns a pointer to a statically allocated string
-  containing a lowercased version of the input string. Do not free
-  or modify the returned string! Since the returned string is statically
-  allocated, it will be modified at each function call (not re-entrant).
- */
-/*--------------------------------------------------------------------------*/
-static char *
-strlwc(const char *s)
-{
-  static char l[ASCIILINESZ + 1];
-  int i;
-
-  if (s == NULL) {
-    return NULL;
-  }
-  memset(l, 0, ASCIILINESZ + 1);
-  i = 0;
-  while (s[i] && i < ASCIILINESZ) {
-    l[i] = (char)tolower((int)s[i]);
-    i++;
-  }
-  l[ASCIILINESZ] = (char)0;
-  return l;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Remove blanks at the beginning and the end of a string.
-  @param	s	String to parse.
-  @return	ptr to statically allocated string.
-
-  This function returns a pointer to a statically allocated string,
-  which is identical to the input string, except that all blank
-  characters at the end and the beg. of the string have been removed.
-  Do not free or modify the returned string! Since the returned string
-  is statically allocated, it will be modified at each function call
-  (not re-entrant).
- */
-/*--------------------------------------------------------------------------*/
-static char *
-strstrip(char *s)
-{
-  static char l[ASCIILINESZ + 1];
-  char *last;
-
-  if (s == NULL) {
-    return NULL;
-  }
-
-  while (isspace((int)*s) && *s) {
-    s++;
-  }
-  memset(l, 0, ASCIILINESZ + 1);
-  strcpy(l, s);
-  last = l + strlen(l);
-  while (last > l) {
-    if (!isspace((int)*(last - 1))) {
-      break;
-    }
-    last--;
-  }
-  *last = (char)0;
-  return (char *)l;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get number of sections in a dictionary
-  @param    d   Dictionary to examine
-  @return   int Number of sections found in dictionary
-
-  This function returns the number of sections found in a dictionary.
-  The test to recognize sections is done on the string stored in the
-  dictionary: a section name is given as "section" whereas a key is
-  stored as "section:key", thus the test looks for entries that do not
-  contain a colon.
-
-  This clearly fails in the case a section name contains a colon, but
-  this should simply be avoided.
-
-  This function returns -1 in case of error.
- */
-/*--------------------------------------------------------------------------*/
-int
-iniparser_getnsec(dictionary *d)
-{
-  int i;
-  int nsec;
-
-  if (d == NULL) {
-    return -1;
-  }
-  nsec = 0;
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] == NULL) {
-      continue;
-    }
-    if (strchr(d->key[i], ':') == NULL) {
-      nsec++;
-    }
-  }
-  return nsec;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get name for section n in a dictionary.
-  @param    d   Dictionary to examine
-  @param    n   Section number (from 0 to nsec-1).
-  @return   Pointer to char string
-
-  This function locates the n-th section in a dictionary and returns
-  its name as a pointer to a string statically allocated inside the
-  dictionary. Do not free or modify the returned string!
-
-  This function returns NULL in case of error.
- */
-/*--------------------------------------------------------------------------*/
-char *
-iniparser_getsecname(dictionary *d, int n)
-{
-  int i;
-  int foundsec;
-
-  if (d == NULL || n < 0) {
-    return NULL;
-  }
-  foundsec = 0;
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] == NULL) {
-      continue;
-    }
-    if (strchr(d->key[i], ':') == NULL) {
-      foundsec++;
-      if (foundsec > n) {
-        break;
-      }
-    }
-  }
-  if (foundsec <= n) {
-    return NULL;
-  }
-  return d->key[i];
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Dump a dictionary to an opened file pointer.
-  @param    d   Dictionary to dump.
-  @param    f   Opened file pointer to dump to.
-  @return   void
-
-  This function prints out the contents of a dictionary, one element by
-  line, onto the provided file pointer. It is OK to specify @c stderr
-  or @c stdout as output files. This function is meant for debugging
-  purposes mostly.
- */
-/*--------------------------------------------------------------------------*/
-void
-iniparser_dump(dictionary *d, FILE *f)
-{
-  int i;
-
-  if (d == NULL || f == NULL) {
-    return;
-  }
-  for (i = 0; i < d->size; i++) {
-    if (d->key[i] == NULL) {
-      continue;
-    }
-    if (d->val[i] != NULL) {
-      fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
-    } else {
-      fprintf(f, "[%s]=UNDEF\n", d->key[i]);
-    }
-  }
-  return;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Save a dictionary to a loadable ini file
-  @param    d   Dictionary to dump
-  @param    f   Opened file pointer to dump to
-  @return   void
-
-  This function dumps a given dictionary into a loadable ini file.
-  It is Ok to specify @c stderr or @c stdout as output files.
- */
-/*--------------------------------------------------------------------------*/
-void
-iniparser_dump_ini(dictionary *d, FILE *f)
-{
-  int i, j;
-  char keym[ASCIILINESZ + 1];
-  int nsec;
-  char *secname;
-  int seclen;
-
-  if (d == NULL || f == NULL) {
-    return;
-  }
-
-  nsec = iniparser_getnsec(d);
-  if (nsec < 1) {
-    /* No section in file: dump all keys as they are */
-    for (i = 0; i < d->size; i++) {
-      if (d->key[i] == NULL) {
-        continue;
-      }
-      fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
-    }
-    return;
-  }
-  for (i = 0; i < nsec; i++) {
-    secname = iniparser_getsecname(d, i);
-    seclen  = (int)strlen(secname);
-    fprintf(f, "\n[%s]\n", secname);
-    sprintf(keym, "%s:", secname);
-    for (j = 0; j < d->size; j++) {
-      if (d->key[j] == NULL) {
-        continue;
-      }
-      if (!strncmp(d->key[j], keym, seclen + 1)) {
-        fprintf(f, "%-30s = %s\n", d->key[j] + seclen + 1, d->val[j] ? d->val[j] : "");
-      }
-    }
-  }
-  fprintf(f, "\n");
-  return;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key
-  @param    d       Dictionary to search
-  @param    key     Key string to look for
-  @param    def     Default value to return if key not found.
-  @return   pointer to statically allocated character string
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the pointer passed as 'def' is returned.
-  The returned char pointer is pointing to a string allocated in
-  the dictionary, do not free or modify it.
- */
-/*--------------------------------------------------------------------------*/
-char *
-iniparser_getstring(dictionary *d, const char *key, char *def)
-{
-  char *lc_key;
-  char *sval;
-
-  if (d == NULL || key == NULL) {
-    return def;
-  }
-
-  lc_key = strlwc(key);
-  sval   = dictionary_get(d, lc_key, def);
-  return sval;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key, convert to an int
-  @param    d Dictionary to search
-  @param    key Key string to look for
-  @param    notfound Value to return in case of error
-  @return   integer
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the notfound value is returned.
-
-  Supported values for integers include the usual C notation
-  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
-  are supported. Examples:
-
-  "42"      ->  42
-  "042"     ->  34 (octal -> decimal)
-  "0x42"    ->  66 (hexa  -> decimal)
-
-  Warning: the conversion may overflow in various ways. Conversion is
-  totally outsourced to strtol(), see the associated man page for overflow
-  handling.
-
-  Credits: Thanks to A. Becker for suggesting strtol()
- */
-/*--------------------------------------------------------------------------*/
-int
-iniparser_getint(dictionary *d, const char *key, int notfound)
-{
-  char *str;
-
-  str = iniparser_getstring(d, key, INI_INVALID_KEY);
-  if (str == INI_INVALID_KEY) {
-    return notfound;
-  }
-  return (int)strtol(str, NULL, 0);
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key, convert to a double
-  @param    d Dictionary to search
-  @param    key Key string to look for
-  @param    notfound Value to return in case of error
-  @return   double
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the notfound value is returned.
- */
-/*--------------------------------------------------------------------------*/
-double
-iniparser_getdouble(dictionary *d, char *key, double notfound)
-{
-  char *str;
-
-  str = iniparser_getstring(d, key, INI_INVALID_KEY);
-  if (str == INI_INVALID_KEY) {
-    return notfound;
-  }
-  return atof(str);
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key, convert to a boolean
-  @param    d Dictionary to search
-  @param    key Key string to look for
-  @param    notfound Value to return in case of error
-  @return   integer
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the notfound value is returned.
-
-  A true boolean is found if one of the following is matched:
-
-  - A string starting with 'y'
-  - A string starting with 'Y'
-  - A string starting with 't'
-  - A string starting with 'T'
-  - A string starting with '1'
-
-  A false boolean is found if one of the following is matched:
-
-  - A string starting with 'n'
-  - A string starting with 'N'
-  - A string starting with 'f'
-  - A string starting with 'F'
-  - A string starting with '0'
-
-  The notfound value returned if no boolean is identified, does not
-  necessarily have to be 0 or 1.
- */
-/*--------------------------------------------------------------------------*/
-int
-iniparser_getboolean(dictionary *d, const char *key, int notfound)
-{
-  char *c;
-  int ret;
-
-  c = iniparser_getstring(d, key, INI_INVALID_KEY);
-  if (c == INI_INVALID_KEY) {
-    return notfound;
-  }
-  if (c[0] == 'y' || c[0] == 'Y' || c[0] == '1' || c[0] == 't' || c[0] == 'T') {
-    ret = 1;
-  } else if (c[0] == 'n' || c[0] == 'N' || c[0] == '0' || c[0] == 'f' || c[0] == 'F') {
-    ret = 0;
-  } else {
-    ret = notfound;
-  }
-  return ret;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Finds out if a given entry exists in a dictionary
-  @param    ini     Dictionary to search
-  @param    entry   Name of the entry to look for
-  @return   integer 1 if entry exists, 0 otherwise
-
-  Finds out if a given entry exists in the dictionary. Since sections
-  are stored as keys with NULL associated values, this is the only way
-  of querying for the presence of sections in a dictionary.
- */
-/*--------------------------------------------------------------------------*/
-int
-iniparser_find_entry(dictionary *ini, char *entry)
-{
-  int found = 0;
-  if (iniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) {
-    found = 1;
-  }
-  return found;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Set an entry in a dictionary.
-  @param    ini     Dictionary to modify.
-  @param    entry   Entry to modify (entry name)
-  @param    val     New value to associate to the entry.
-  @return   int 0 if Ok, -1 otherwise.
-
-  If the given entry can be found in the dictionary, it is modified to
-  contain the provided value. If it cannot be found, -1 is returned.
-  It is Ok to set val to NULL.
- */
-/*--------------------------------------------------------------------------*/
-int
-iniparser_set(dictionary *ini, char *entry, char *val)
-{
-  return dictionary_set(ini, strlwc(entry), val);
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Delete an entry in a dictionary
-  @param    ini     Dictionary to modify
-  @param    entry   Entry to delete (entry name)
-  @return   void
-
-  If the given entry can be found, it is deleted from the dictionary.
- */
-/*--------------------------------------------------------------------------*/
-void
-iniparser_unset(dictionary *ini, char *entry)
-{
-  dictionary_unset(ini, strlwc(entry));
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief	Load a single line from an INI file
-  @param    input_line  Input line, may be concatenated multi-line input
-  @param    section     Output space to store section
-  @param    key         Output space to store key
-  @param    value       Output space to store value
-  @return   line_status value
- */
-/*--------------------------------------------------------------------------*/
-static line_status
-iniparser_line(char *input_line, char *section, char *key, char *value)
-{
-  line_status sta;
-  char line[ASCIILINESZ + 1];
-  int len;
-
-  strcpy(line, strstrip(input_line));
-  len = (int)strlen(line);
-
-  sta = LINE_UNPROCESSED;
-  if (len < 1) {
-    /* Empty line */
-    sta = LINE_EMPTY;
-  } else if (line[0] == '#') {
-    /* Comment line */
-    sta = LINE_COMMENT;
-  } else if (line[0] == '[' && line[len - 1] == ']') {
-    /* Section name */
-    sscanf(line, "[%[^]]", section);
-    strcpy(section, strstrip(section));
-    strcpy(section, strlwc(section));
-    sta = LINE_SECTION;
-  } else if (sscanf(line, "%[^=] = \"%[^\"]\"", key, value) == 2 || sscanf(line, "%[^=] = '%[^\']'", key, value) == 2 ||
-             sscanf(line, "%[^=] = %[^;#]", key, value) == 2) {
-    /* Usual key=value, with or without comments */
-    strcpy(key, strstrip(key));
-    strcpy(key, strlwc(key));
-    strcpy(value, strstrip(value));
-    /*
-     * sscanf cannot handle '' or "" as empty values
-     * this is done here
-     */
-    if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
-      value[0] = 0;
-    }
-    sta = LINE_VALUE;
-  } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2 || sscanf(line, "%[^=] %[=]", key, value) == 2) {
-    /*
-     * Special cases:
-     * key=
-     * key=;
-     * key=#
-     */
-    strcpy(key, strstrip(key));
-    strcpy(key, strlwc(key));
-    value[0] = 0;
-    sta      = LINE_VALUE;
-  } else {
-    /* Generate syntax error */
-    sta = LINE_ERROR;
-  }
-  return sta;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Parse an ini file and return an allocated dictionary object
-  @param    ininame Name of the ini file to read.
-  @return   Pointer to newly allocated dictionary
-
-  This is the parser for ini files. This function is called, providing
-  the name of the file to be read. It returns a dictionary object that
-  should not be accessed directly, but through accessor functions
-  instead.
-
-  The returned dictionary must be freed using iniparser_freedict().
- */
-/*--------------------------------------------------------------------------*/
-dictionary *
-iniparser_load(const char *ininame)
-{
-  FILE *in;
-
-  char line[ASCIILINESZ + 1];
-  char section[ASCIILINESZ + 1];
-  char key[ASCIILINESZ + 1];
-  char tmp[ASCIILINESZ + 1];
-  char val[ASCIILINESZ + 1];
-
-  int last = 0;
-  int len;
-  int lineno = 0;
-  int errs   = 0;
-
-  dictionary *dict;
-
-  if ((in = fopen(ininame, "r")) == NULL) {
-    fprintf(stderr, "iniparser: cannot open %s\n", ininame);
-    return NULL;
-  }
-
-  dict = dictionary_new(0);
-  if (!dict) {
-    fclose(in);
-    return NULL;
-  }
-
-  memset(line, 0, ASCIILINESZ);
-  memset(section, 0, ASCIILINESZ);
-  memset(key, 0, ASCIILINESZ);
-  memset(val, 0, ASCIILINESZ);
-  last = 0;
-
-  while (fgets(line + last, ASCIILINESZ - last, in) != NULL) {
-    lineno++;
-    len = (int)strlen(line) - 1;
-    /* Safety check against buffer overflows */
-    if (line[len] != '\n') {
-      fprintf(stderr, "iniparser: input line too long in %s (%d)\n", ininame, lineno);
-      dictionary_del(dict);
-      fclose(in);
-      return NULL;
-    }
-    /* Get rid of \n and spaces at end of line */
-    while ((len >= 0) && ((line[len] == '\n') || (isspace(line[len])))) {
-      line[len] = 0;
-      len--;
-    }
-    /* Detect multi-line */
-    if (line[len] == '\\') {
-      /* Multi-line value */
-      last = len;
-      continue;
-    } else {
-      last = 0;
-    }
-    switch (iniparser_line(line, section, key, val)) {
-    case LINE_EMPTY:
-    case LINE_COMMENT:
-      break;
-
-    case LINE_SECTION:
-      errs = dictionary_set(dict, section, NULL);
-      break;
-
-    case LINE_VALUE:
-      snprintf(tmp, sizeof(tmp), "%s:%s", section, key);
-      errs = dictionary_set(dict, tmp, val);
-      break;
-
-    case LINE_ERROR:
-      fprintf(stderr, "iniparser: syntax error in %s (%d):\n", ininame, lineno);
-      fprintf(stderr, "-> %s\n", line);
-      errs++;
-      break;
-
-    default:
-      break;
-    }
-    memset(line, 0, ASCIILINESZ);
-    last = 0;
-    if (errs < 0) {
-      fprintf(stderr, "iniparser: memory allocation failure\n");
-      break;
-    }
-  }
-  if (errs) {
-    dictionary_del(dict);
-    dict = NULL;
-  }
-  fclose(in);
-  return dict;
-}
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Free all memory associated to an ini dictionary
-  @param    d Dictionary to free
-  @return   void
-
-  Free all memory associated to an ini dictionary.
-  It is mandatory to call this function before the dictionary object
-  gets out of the current context.
- */
-/*--------------------------------------------------------------------------*/
-void
-iniparser_freedict(dictionary *d)
-{
-  dictionary_del(d);
-}
-
-/* vim: set ts=4 et sw=4 tw=75 */
diff --git a/plugins/experimental/mysql_remap/lib/iniparser.h b/plugins/experimental/mysql_remap/lib/iniparser.h
deleted file mode 100644
index ca415be..0000000
--- a/plugins/experimental/mysql_remap/lib/iniparser.h
+++ /dev/null
@@ -1,295 +0,0 @@
-/** @file
-
-  A brief file description
-
-  @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.
- */
-
-/*-------------------------------------------------------------------------*/
-/**
-   @file    iniparser.h
-   @author  N. Devillard
-   @date    Sep 2007
-   @version 3.0
-   @brief   Parser for ini files.
-*/
-/*--------------------------------------------------------------------------*/
-
-/*
-        $Id: iniparser.h,v 1.24 2007-11-23 21:38:19 ndevilla Exp $
-        $Revision: 1.24 $
-*/
-
-#pragma once
-
-/*---------------------------------------------------------------------------
-                                                                Includes
- ---------------------------------------------------------------------------*/
-
-#include <stdio.h>  // NOLINT(modernize-deprecated-headers)
-#include <stdlib.h> // NOLINT(modernize-deprecated-headers)
-#include <string.h> // NOLINT(modernize-deprecated-headers)
-
-/*
- * The following #include is necessary on many Unixes but not Linux.
- * It is not needed for Windows platforms.
- * Uncomment it if needed.
- */
-/* #include <unistd.h> */
-
-#include "dictionary.h"
-
-/*---------------------------------------------------------------------------
-                                                                Macros
- ---------------------------------------------------------------------------*/
-/** For backwards compatibility only */
-#define iniparser_getstr(d, k) iniparser_getstring(d, k, NULL)
-#define iniparser_setstr iniparser_setstring
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get number of sections in a dictionary
-  @param    d   Dictionary to examine
-  @return   int Number of sections found in dictionary
-
-  This function returns the number of sections found in a dictionary.
-  The test to recognize sections is done on the string stored in the
-  dictionary: a section name is given as "section" whereas a key is
-  stored as "section:key", thus the test looks for entries that do not
-  contain a colon.
-
-  This clearly fails in the case a section name contains a colon, but
-  this should simply be avoided.
-
-  This function returns -1 in case of error.
- */
-/*--------------------------------------------------------------------------*/
-
-int iniparser_getnsec(dictionary *d);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get name for section n in a dictionary.
-  @param    d   Dictionary to examine
-  @param    n   Section number (from 0 to nsec-1).
-  @return   Pointer to char string
-
-  This function locates the n-th section in a dictionary and returns
-  its name as a pointer to a string statically allocated inside the
-  dictionary. Do not free or modify the returned string!
-
-  This function returns NULL in case of error.
- */
-/*--------------------------------------------------------------------------*/
-
-char *iniparser_getsecname(dictionary *d, int n);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Save a dictionary to a loadable ini file
-  @param    d   Dictionary to dump
-  @param    f   Opened file pointer to dump to
-  @return   void
-
-  This function dumps a given dictionary into a loadable ini file.
-  It is Ok to specify @c stderr or @c stdout as output files.
- */
-/*--------------------------------------------------------------------------*/
-
-void iniparser_dump_ini(dictionary *d, FILE *f);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Dump a dictionary to an opened file pointer.
-  @param    d   Dictionary to dump.
-  @param    f   Opened file pointer to dump to.
-  @return   void
-
-  This function prints out the contents of a dictionary, one element by
-  line, onto the provided file pointer. It is OK to specify @c stderr
-  or @c stdout as output files. This function is meant for debugging
-  purposes mostly.
- */
-/*--------------------------------------------------------------------------*/
-void iniparser_dump(dictionary *d, FILE *f);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key
-  @param    d       Dictionary to search
-  @param    key     Key string to look for
-  @param    def     Default value to return if key not found.
-  @return   pointer to statically allocated character string
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the pointer passed as 'def' is returned.
-  The returned char pointer is pointing to a string allocated in
-  the dictionary, do not free or modify it.
- */
-/*--------------------------------------------------------------------------*/
-char *iniparser_getstring(dictionary *d, const char *key, char *def);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key, convert to an int
-  @param    d Dictionary to search
-  @param    key Key string to look for
-  @param    notfound Value to return in case of error
-  @return   integer
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the notfound value is returned.
-
-  Supported values for integers include the usual C notation
-  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
-  are supported. Examples:
-
-  - "42"      ->  42
-  - "042"     ->  34 (octal -> decimal)
-  - "0x42"    ->  66 (hexa  -> decimal)
-
-  Warning: the conversion may overflow in various ways. Conversion is
-  totally outsourced to strtol(), see the associated man page for overflow
-  handling.
-
-  Credits: Thanks to A. Becker for suggesting strtol()
- */
-/*--------------------------------------------------------------------------*/
-int iniparser_getint(dictionary *d, const char *key, int notfound);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key, convert to a double
-  @param    d Dictionary to search
-  @param    key Key string to look for
-  @param    notfound Value to return in case of error
-  @return   double
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the notfound value is returned.
- */
-/*--------------------------------------------------------------------------*/
-double iniparser_getdouble(dictionary *d, char *key, double notfound);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Get the string associated to a key, convert to a boolean
-  @param    d Dictionary to search
-  @param    key Key string to look for
-  @param    notfound Value to return in case of error
-  @return   integer
-
-  This function queries a dictionary for a key. A key as read from an
-  ini file is given as "section:key". If the key cannot be found,
-  the notfound value is returned.
-
-  A true boolean is found if one of the following is matched:
-
-  - A string starting with 'y'
-  - A string starting with 'Y'
-  - A string starting with 't'
-  - A string starting with 'T'
-  - A string starting with '1'
-
-  A false boolean is found if one of the following is matched:
-
-  - A string starting with 'n'
-  - A string starting with 'N'
-  - A string starting with 'f'
-  - A string starting with 'F'
-  - A string starting with '0'
-
-  The notfound value returned if no boolean is identified, does not
-  necessarily have to be 0 or 1.
- */
-/*--------------------------------------------------------------------------*/
-int iniparser_getboolean(dictionary *d, const char *key, int notfound);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Set an entry in a dictionary.
-  @param    ini     Dictionary to modify.
-  @param    entry   Entry to modify (entry name)
-  @param    val     New value to associate to the entry.
-  @return   int 0 if Ok, -1 otherwise.
-
-  If the given entry can be found in the dictionary, it is modified to
-  contain the provided value. If it cannot be found, -1 is returned.
-  It is Ok to set val to NULL.
- */
-/*--------------------------------------------------------------------------*/
-int iniparser_setstring(dictionary *ini, char *entry, char *val);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Delete an entry in a dictionary
-  @param    ini     Dictionary to modify
-  @param    entry   Entry to delete (entry name)
-  @return   void
-
-  If the given entry can be found, it is deleted from the dictionary.
- */
-/*--------------------------------------------------------------------------*/
-void iniparser_unset(dictionary *ini, char *entry);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Finds out if a given entry exists in a dictionary
-  @param    ini     Dictionary to search
-  @param    entry   Name of the entry to look for
-  @return   integer 1 if entry exists, 0 otherwise
-
-  Finds out if a given entry exists in the dictionary. Since sections
-  are stored as keys with NULL associated values, this is the only way
-  of querying for the presence of sections in a dictionary.
- */
-/*--------------------------------------------------------------------------*/
-int iniparser_find_entry(dictionary *ini, char *entry);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Parse an ini file and return an allocated dictionary object
-  @param    ininame Name of the ini file to read.
-  @return   Pointer to newly allocated dictionary
-
-  This is the parser for ini files. This function is called, providing
-  the name of the file to be read. It returns a dictionary object that
-  should not be accessed directly, but through accessor functions
-  instead.
-
-  The returned dictionary must be freed using iniparser_freedict().
- */
-/*--------------------------------------------------------------------------*/
-dictionary *iniparser_load(const char *ininame);
-
-/*-------------------------------------------------------------------------*/
-/**
-  @brief    Free all memory associated to an ini dictionary
-  @param    d Dictionary to free
-  @return   void
-
-  Free all memory associated to an ini dictionary.
-  It is mandatory to call this function before the dictionary object
-  gets out of the current context.
- */
-/*--------------------------------------------------------------------------*/
-void iniparser_freedict(dictionary *d);
diff --git a/plugins/experimental/mysql_remap/mysql_remap.cc b/plugins/experimental/mysql_remap/mysql_remap.cc
deleted file mode 100644
index 95cd1343..0000000
--- a/plugins/experimental/mysql_remap/mysql_remap.cc
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
-  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 <ts/ts.h>
-#include <ts/remap.h>
-#include <cstdio>
-#include <unistd.h>
-
-#include <mysql/mysql.h>
-
-#include "lib/iniparser.h"
-#include "default.h"
-
-MYSQL mysql;
-
-using my_data = struct {
-  char *query;
-};
-
-bool
-do_mysql_remap(TSCont contp, TSHttpTxn txnp)
-{
-  TSMBuffer reqp;
-  TSMLoc hdr_loc, url_loc, field_loc;
-  bool ret_val = false;
-
-  const char *request_host;
-  int request_host_length = 0;
-  const char *request_scheme;
-  int request_scheme_length = 0;
-  int request_port          = 80;
-  char *query;
-
-  MYSQL_ROW row;
-  MYSQL_RES *res;
-
-  my_data *data = static_cast<my_data *>(TSContDataGet(contp));
-  query         = data->query;
-
-  if (TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc) != TS_SUCCESS) {
-    TSDebug(PLUGIN_NAME, "could not get request data");
-    return false;
-  }
-
-  TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc);
-
-  if (!url_loc) {
-    TSDebug(PLUGIN_NAME, "couldn't retrieve request url");
-    goto release_hdr;
-  }
-
-  field_loc = TSMimeHdrFieldFind(reqp, hdr_loc, TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST);
-
-  if (!field_loc) {
-    TSDebug(PLUGIN_NAME, "couldn't retrieve request HOST header");
-    goto release_url;
-  }
-
-  request_host = TSMimeHdrFieldValueStringGet(reqp, hdr_loc, field_loc, -1, &request_host_length);
-  if (!request_host_length) {
-    TSDebug(PLUGIN_NAME, "couldn't find request HOST header");
-    goto release_field;
-  }
-
-  request_scheme = TSUrlSchemeGet(reqp, url_loc, &request_scheme_length);
-  request_port   = TSUrlPortGet(reqp, url_loc);
-
-  TSDebug(PLUGIN_NAME, "      +++++MYSQL REMAP+++++      ");
-
-  TSDebug(PLUGIN_NAME, "\nINCOMING REQUEST ->\n ::: from_scheme_desc: %.*s\n ::: from_hostname: %.*s\n ::: from_port: %d",
-          request_scheme_length, request_scheme, request_host_length, request_host, request_port);
-
-  snprintf(query, QSIZE, " \
-    SELECT \
-        t_scheme.scheme_desc, \
-        t_host.hostname, \
-        to_port \
-      FROM map \
-        INNER JOIN scheme as t_scheme ON (map.to_scheme_id = t_scheme.id) \
-        INNER JOIN scheme as f_scheme ON (map.from_scheme_id = f_scheme.id) \
-        INNER JOIN hostname as t_host ON (map.to_hostname_id = t_host.id) \
-        INNER JOIN hostname as f_host ON (map.from_hostname_id = f_host.id) \
-      WHERE \
-        is_enabled=1 \
-        AND f_host.hostname = '%.*s' \
-        AND f_scheme.id = %d \
-        AND from_port = %d \
-      LIMIT 1",
-           request_host_length, request_host, (strcmp(request_scheme, "https") == 0) ? 2 : 1, request_port);
-
-  mysql_real_query(&mysql, query, (unsigned int)strlen(query));
-  res = mysql_use_result(&mysql);
-
-  if (!res)
-    goto not_found; // TODO: define a fallback
-
-  do {
-    row = mysql_fetch_row(res);
-    if (!row)
-      goto not_found;
-    TSDebug(PLUGIN_NAME, "\nOUTGOING REQUEST ->\n ::: to_scheme_desc: %s\n ::: to_hostname: %s\n ::: to_port: %s", row[0], row[1],
-            row[2]);
-    TSMimeHdrFieldValueStringSet(reqp, hdr_loc, field_loc, 0, row[1], -1);
-    TSUrlHostSet(reqp, url_loc, row[1], -1);
-    TSUrlSchemeSet(reqp, url_loc, row[0], -1);
-    TSUrlPortSet(reqp, url_loc, atoi(row[2]));
-  } while (false);
-
-  ret_val = true;
-
-not_found:
-  if (!ret_val) {
-    // lets build up a nice 404 message for someone
-    TSHttpHdrStatusSet(reqp, hdr_loc, TS_HTTP_STATUS_NOT_FOUND);
-    TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_NOT_FOUND);
-  }
-  if (res) {
-    mysql_free_result(res);
-  }
-release_field:
-  if (field_loc) {
-    TSHandleMLocRelease(reqp, hdr_loc, field_loc);
-  }
-release_url:
-  if (url_loc) {
-    TSHandleMLocRelease(reqp, hdr_loc, url_loc);
-  }
-release_hdr:
-  if (hdr_loc) {
-    TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
-  }
-
-  return ret_val;
-}
-
-static int
-mysql_remap(TSCont contp, TSEvent event, void *edata)
-{
-  TSHttpTxn txnp   = static_cast<TSHttpTxn>(edata);
-  TSEvent reenable = TS_EVENT_HTTP_CONTINUE;
-
-  switch (event) {
-  case TS_EVENT_HTTP_READ_REQUEST_HDR:
-    TSDebug(PLUGIN_NAME, "Reading Request");
-    TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SKIP_REMAPPING, true);
-    if (!do_mysql_remap(contp, txnp)) {
-      reenable = TS_EVENT_HTTP_ERROR;
-    }
-    break;
-  default:
-    break;
-  }
-
-  TSHttpTxnReenable(txnp, reenable);
-  return 1;
-}
-
-void
-TSPluginInit(int argc, const char *argv[])
-{
-  dictionary *ini;
-  const char *host;
-  int port;
-  const char *username;
-  const char *password;
-  const char *db;
-
-  my_data *data = static_cast<my_data *>(malloc(1 * sizeof(my_data)));
-
-  TSPluginRegistrationInfo info;
-  bool reconnect = true;
-
-  info.plugin_name   = const_cast<char *>(PLUGIN_NAME);
-  info.vendor_name   = const_cast<char *>("Apache Software Foundation");
-  info.support_email = const_cast<char *>("dev@trafficserver.apache.org");
-
-  if (TSPluginRegister(&info) != TS_SUCCESS) {
-    TSError("[mysql_remap] Plugin registration failed");
-  }
-
-  if (argc != 2) {
-    TSError("[mysql_remap] Usage: %s /path/to/sample.ini", argv[0]);
-    return;
-  }
-
-  ini = iniparser_load(argv[1]);
-  if (!ini) {
-    TSError("[mysql_remap] Error with ini file (1)");
-    TSDebug(PLUGIN_NAME, "Error parsing ini file(1)");
-    return;
-  }
-
-  host     = iniparser_getstring(ini, "mysql_remap:mysql_host", (char *)"localhost");
-  port     = iniparser_getint(ini, "mysql_remap:mysql_port", 3306);
-  username = iniparser_getstring(ini, "mysql_remap:mysql_username", nullptr);
-  password = iniparser_getstring(ini, "mysql_remap:mysql_password", nullptr);
-  db       = iniparser_getstring(ini, "mysql_remap:mysql_database", (char *)"mysql_remap");
-
-  if (mysql_library_init(0, NULL, NULL)) {
-    TSError("[mysql_remap] Error initializing mysql client library");
-    TSDebug(PLUGIN_NAME, "Error initializing mysql client library");
-    return;
-  }
-
-  if (!mysql_init(&mysql)) {
-    TSError("[mysql_remap] Could not initialize MySQL");
-    TSDebug(PLUGIN_NAME, "Could not initialize MySQL");
-    return;
-  }
-
-  mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect);
-
-  if (!mysql_real_connect(&mysql, host, username, password, db, port, NULL, 0)) {
-    TSError("[mysql_remap] Could not connect to mysql");
-    TSDebug(PLUGIN_NAME, "Could not connect to mysql: %s", mysql_error(&mysql));
-    return;
-  }
-
-  data->query = static_cast<char *>(TSmalloc(QSIZE * sizeof(char))); // TODO: malloc smarter sizes
-
-  TSDebug(PLUGIN_NAME, "h: %s; u: %s; p: %s; p:%d; d:%s", host, username, password, port, db);
-  TSCont cont = TSContCreate(mysql_remap, TSMutexCreate());
-
-  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
-
-  TSContDataSet(cont, (void *)data);
-
-  TSDebug(PLUGIN_NAME, "plugin is successfully initialized [plugin mode]");
-  iniparser_freedict(ini);
-  return;
-}
diff --git a/plugins/experimental/mysql_remap/sample.ini b/plugins/experimental/mysql_remap/sample.ini
deleted file mode 100644
index 912090c..0000000
--- a/plugins/experimental/mysql_remap/sample.ini
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# 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.
-#
-
-[mysql_remap]
-mysql_host     = localhost   #default
-mysql_port     = 3306        #default
-mysql_username = root
-mysql_password =
-mysql_database = mysql_remap #default
diff --git a/plugins/experimental/mysql_remap/schema/import.sql b/plugins/experimental/mysql_remap/schema/import.sql
deleted file mode 100644
index cbe331b..0000000
--- a/plugins/experimental/mysql_remap/schema/import.sql
+++ /dev/null
@@ -1,134 +0,0 @@
---
--- 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.
---
-
--- MySQL dump 10.13  Distrib 5.1.48, for apple-darwin10.4.0 (i386)
---
--- Host: localhost    Database: mysql_remap
--- ------------------------------------------------------
--- Server version	5.1.48-log
-
-/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
-/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
-/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
-/*!40101 SET NAMES utf8 */;
-/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
-/*!40103 SET TIME_ZONE='+00:00' */;
-/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
-/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
-/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
-/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-
---
--- Table structure for table `hostname`
---
-
-DROP TABLE IF EXISTS `hostname`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `hostname` (
-  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
-  `hostname` varchar(255) NOT NULL,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `hostname` (`hostname`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
---
--- Dumping data for table `hostname`
---
-
-LOCK TABLES `hostname` WRITE;
-/*!40000 ALTER TABLE `hostname` DISABLE KEYS */;
-INSERT INTO `hostname` VALUES (1,'www.ericbalsa.com'),(2,'www.google.com');
-/*!40000 ALTER TABLE `hostname` ENABLE KEYS */;
-UNLOCK TABLES;
-
---
--- Table structure for table `map`
---
-
-DROP TABLE IF EXISTS `map`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `map` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `from_scheme_id` int(10) unsigned NOT NULL DEFAULT '1',
-  `from_hostname_id` int(10) unsigned NOT NULL,
-  `from_port` int(5) unsigned NOT NULL,
-  `to_scheme_id` int(10) unsigned NOT NULL DEFAULT '1',
-  `to_hostname_id` int(10) unsigned NOT NULL,
-  `to_port` int(5) unsigned NOT NULL DEFAULT '80',
-  `is_enabled` tinyint(1) unsigned NOT NULL DEFAULT '1',
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `from_unique` (`from_scheme_id`,`from_hostname_id`,`from_port`),
-  UNIQUE KEY `to_unique` (`to_scheme_id`,`to_hostname_id`,`to_port`),
-  UNIQUE KEY `unique_across_everything` (`from_scheme_id`,`from_hostname_id`,`from_port`,`to_scheme_id`,`to_hostname_id`,`to_port`),
-  KEY `to_hostname_id` (`to_hostname_id`),
-  KEY `from_hostname_id` (`from_hostname_id`),
-  CONSTRAINT `map_ibfk_1` FOREIGN KEY (`from_scheme_id`) REFERENCES `scheme` (`id`) ON UPDATE CASCADE,
-  CONSTRAINT `map_ibfk_3` FOREIGN KEY (`to_scheme_id`) REFERENCES `scheme` (`id`) ON UPDATE CASCADE,
-  CONSTRAINT `map_ibfk_4` FOREIGN KEY (`to_hostname_id`) REFERENCES `hostname` (`id`) ON UPDATE CASCADE,
-  CONSTRAINT `map_ibfk_5` FOREIGN KEY (`from_hostname_id`) REFERENCES `hostname` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
---
--- Dumping data for table `map`
---
-
-LOCK TABLES `map` WRITE;
-/*!40000 ALTER TABLE `map` DISABLE KEYS */;
-INSERT INTO `map` VALUES (1,1,2,80,1,1,80,1);
-/*!40000 ALTER TABLE `map` ENABLE KEYS */;
-UNLOCK TABLES;
-
---
--- Table structure for table `scheme`
---
-
-DROP TABLE IF EXISTS `scheme`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `scheme` (
-  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `scheme_desc` varchar(5) NOT NULL,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `desc` (`scheme_desc`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
---
--- Dumping data for table `scheme`
---
-
-LOCK TABLES `scheme` WRITE;
-/*!40000 ALTER TABLE `scheme` DISABLE KEYS */;
-INSERT INTO `scheme` VALUES (1,'http'),(2,'https');
-/*!40000 ALTER TABLE `scheme` ENABLE KEYS */;
-UNLOCK TABLES;
-/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
-
-/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
-/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
-/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
-/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
-/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
-/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
-/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-
--- Dump completed on 2010-11-15 21:08:43
diff --git a/plugins/lua/ts_lua_http.c b/plugins/lua/ts_lua_http.c
index e0249bd..c361b65 100644
--- a/plugins/lua/ts_lua_http.c
+++ b/plugins/lua/ts_lua_http.c
@@ -794,8 +794,8 @@
   ts_lua_http_ctx *http_ctx;
 
   GET_HTTP_CONTEXT(http_ctx, L);
-
-  if (TSHttpTxnAborted(http_ctx->txnp)) {
+  bool client_abort = false;
+  if (TSHttpTxnAborted(http_ctx->txnp, &client_abort)) {
     lua_pushnumber(L, 1);
   } else {
     lua_pushnumber(L, 0);
diff --git a/plugins/lua/ts_lua_http_config.c b/plugins/lua/ts_lua_http_config.c
index 61e6677..95d3e2f 100644
--- a/plugins/lua/ts_lua_http_config.c
+++ b/plugins/lua/ts_lua_http_config.c
@@ -72,9 +72,7 @@
   TS_LUA_CONFIG_HTTP_CONNECT_DEAD_POLICY                      = TS_CONFIG_HTTP_CONNECT_DEAD_POLICY,
   TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES              = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES,
   TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT                 = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT,
-  TS_LUA_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT            = TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT,
   TS_LUA_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME                   = TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME,
-  TS_LUA_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD              = TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD,
   TS_LUA_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS                    = TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS,
   TS_LUA_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT           = TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT,
   TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_STR                      = TS_CONFIG_HTTP_RESPONSE_SERVER_STR,
@@ -130,27 +128,24 @@
   TS_LUA_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD              = TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD,
   TS_LUA_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME                  = TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME,
   TS_LUA_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS              = TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS,
-  TS_LUA_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT           = TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT,
   TS_LUA_CONFIG_HTTP_ALLOW_MULTI_RANGE                        = TS_CONFIG_HTTP_ALLOW_MULTI_RANGE,
   TS_LUA_CONFIG_HTTP_REQUEST_BUFFER_ENABLED                   = TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED,
   TS_LUA_CONFIG_HTTP_ALLOW_HALF_OPEN                          = TS_CONFIG_HTTP_ALLOW_HALF_OPEN,
-#if TS_VERSION_MAJOR < 10
-  TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER,
-#endif
-  TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY        = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY,
-  TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES    = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES,
-  TS_LUA_CONFIG_SSL_CLIENT_SNI_POLICY                  = TS_CONFIG_SSL_CLIENT_SNI_POLICY,
-  TS_LUA_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME        = TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME,
-  TS_LUA_CONFIG_SSL_CLIENT_CA_CERT_FILENAME            = TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME,
-  TS_LUA_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE        = TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE,
-  TS_LUA_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX         = TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX,
-  TS_LUA_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK    = TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK,
-  TS_LUA_CONFIG_NET_SOCK_NOTSENT_LOWAT                 = TS_CONFIG_NET_SOCK_NOTSENT_LOWAT,
-  TS_LUA_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE = TS_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE,
-  TS_LUA_CONFIG_ENABLE_PARENT_TIMEOUT_MARKDOWNS        = TS_CONFIG_HTTP_ENABLE_PARENT_TIMEOUT_MARKDOWNS,
-  TS_LUA_CONFIG_DISABLE_PARENT_MARKDOWNS               = TS_CONFIG_HTTP_DISABLE_PARENT_MARKDOWNS,
-  TS_LUA_CONFIG_NET_DEFAULT_INACTIVITY_TIMEOUT         = TS_CONFIG_NET_DEFAULT_INACTIVITY_TIMEOUT,
-  TS_LUA_CONFIG_LAST_ENTRY                             = TS_CONFIG_LAST_ENTRY,
+  TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY               = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY,
+  TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES           = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES,
+  TS_LUA_CONFIG_SSL_CLIENT_SNI_POLICY                         = TS_CONFIG_SSL_CLIENT_SNI_POLICY,
+  TS_LUA_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME               = TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME,
+  TS_LUA_CONFIG_SSL_CLIENT_CA_CERT_FILENAME                   = TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME,
+  TS_LUA_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS                     = TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS,
+  TS_LUA_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE               = TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE,
+  TS_LUA_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX                = TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX,
+  TS_LUA_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK           = TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK,
+  TS_LUA_CONFIG_NET_SOCK_NOTSENT_LOWAT                        = TS_CONFIG_NET_SOCK_NOTSENT_LOWAT,
+  TS_LUA_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE        = TS_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE,
+  TS_LUA_CONFIG_ENABLE_PARENT_TIMEOUT_MARKDOWNS               = TS_CONFIG_HTTP_ENABLE_PARENT_TIMEOUT_MARKDOWNS,
+  TS_LUA_CONFIG_DISABLE_PARENT_MARKDOWNS                      = TS_CONFIG_HTTP_DISABLE_PARENT_MARKDOWNS,
+  TS_LUA_CONFIG_NET_DEFAULT_INACTIVITY_TIMEOUT                = TS_CONFIG_NET_DEFAULT_INACTIVITY_TIMEOUT,
+  TS_LUA_CONFIG_LAST_ENTRY                                    = TS_CONFIG_LAST_ENTRY,
 } TSLuaOverridableConfigKey;
 
 typedef enum {
@@ -211,9 +206,7 @@
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_DEAD_POLICY),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT),
-  TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME),
-  TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_STR),
@@ -269,18 +262,15 @@
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS),
-  TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_ALLOW_MULTI_RANGE),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_REQUEST_BUFFER_ENABLED),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_ALLOW_HALF_OPEN),
-#if TS_VERSION_MAJOR < 10
-  TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_VERIFY_SERVER),
-#endif
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_SNI_POLICY),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME),
+  TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_HTTP_SERVER_MIN_KEEP_ALIVE_CONNS),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX),
diff --git a/plugins/stats_over_http/stats_over_http.cc b/plugins/stats_over_http/stats_over_http.cc
index c8ea08b..303d170 100644
--- a/plugins/stats_over_http/stats_over_http.cc
+++ b/plugins/stats_over_http/stats_over_http.cc
@@ -42,14 +42,13 @@
 #include <fstream>
 #include <ts/remap.h>
 
+#include <tscpp/util/ts_ip.h>
+
 #include "ink_autoconf.h"
 #if HAVE_BROTLI_ENCODE_H
 #include <brotli/encode.h>
 #endif
 
-#include "tscore/ink_defs.h"
-#include "tscore/IpMap.h"
-
 #define PLUGIN_NAME "stats_over_http"
 #define FREE_TMOUT 300000
 #define STR_BUFFER_SIZE 1024
@@ -57,8 +56,8 @@
 #define SYSTEM_RECORD_TYPE (0x100)
 #define DEFAULT_RECORD_TYPES (SYSTEM_RECORD_TYPE | TS_RECORDTYPE_PROCESS | TS_RECORDTYPE_PLUGIN)
 
-std::string_view const DEFAULT_IP  = "0.0.0.0/0";
-std::string_view const DEFAULT_IP6 = "::/0";
+static const swoc::IP4Range DEFAULT_IP{swoc::IP4Addr::MIN, swoc::IP4Addr::MAX};
+static const swoc::IP6Range DEFAULT_IP6{swoc::IP6Addr::MIN, swoc::IP6Addr::MAX};
 
 /* global holding the path used for access to this JSON data */
 std::string const DEFAULT_URL_PATH = "_stats";
@@ -92,7 +91,7 @@
 struct config_t {
   unsigned int recordTypes;
   std::string stats_path;
-  IpMap allow_ip_map;
+  ts::IPAddrSet addrs;
 };
 struct config_holder_t {
   char *config_path;
@@ -359,8 +358,7 @@
 }
 
 static void
-json_out_stat(TSRecordType rec_type ATS_UNUSED, void *edata, int registered ATS_UNUSED, const char *name,
-              TSRecordDataType data_type, TSRecordData *datum)
+json_out_stat(TSRecordType rec_type, void *edata, int registered, const char *name, TSRecordDataType data_type, TSRecordData *datum)
 {
   stats_state *my_state = static_cast<stats_state *>(edata);
 
@@ -384,8 +382,7 @@
 }
 
 static void
-csv_out_stat(TSRecordType rec_type ATS_UNUSED, void *edata, int registered ATS_UNUSED, const char *name, TSRecordDataType data_type,
-             TSRecordData *datum)
+csv_out_stat(TSRecordType rec_type, void *edata, int registered, const char *name, TSRecordDataType data_type, TSRecordData *datum)
 {
   stats_state *my_state = static_cast<stats_state *>(edata);
   switch (data_type) {
@@ -737,37 +734,29 @@
     return true;
   }
 
-  if (config->allow_ip_map.contains(addr, nullptr)) {
+  if (config->addrs.contains(swoc::IPAddr(addr))) {
     return true;
   }
 
   return false;
 }
 static void
-parseIpMap(config_t *config, const char *ipStr)
+parseIpMap(config_t *config, swoc::TextView txt)
 {
-  IpAddr min, max;
-  ts::TextView ipstring("");
-
-  if (ipStr != 0) {
-    ipstring.assign(ipStr, strlen(ipStr));
-  }
-
   // sent null ipstring, fill with default open IPs
-  if (ipStr == nullptr) {
-    ats_ip_range_parse(DEFAULT_IP6, min, max);
-    config->allow_ip_map.fill(min, max, nullptr);
-    ats_ip_range_parse(DEFAULT_IP, min, max);
-    config->allow_ip_map.fill(min, max, nullptr);
+  if (txt.empty()) {
+    config->addrs.fill(DEFAULT_IP6);
+    config->addrs.fill(DEFAULT_IP);
     TSDebug(PLUGIN_NAME, "Empty allow settings, setting all IPs in allow list");
     return;
   }
 
-  while (ipstring) {
-    ts::TextView token{ipstring.take_prefix_at(',')};
-    ats_ip_range_parse(std::string_view{token}, min, max);
-    config->allow_ip_map.fill(min, max, nullptr);
-    TSDebug(PLUGIN_NAME, "Added %.*s to allow ip list", int(token.length()), token.data());
+  while (txt) {
+    auto token{txt.take_prefix_at(',')};
+    if (swoc::IPRange r; r.load(token)) {
+      config->addrs.fill(r);
+      TSDebug(PLUGIN_NAME, "Added %.*s to allow ip list", int(token.length()), token.data());
+    }
   }
 }
 
@@ -779,7 +768,6 @@
   config->recordTypes = DEFAULT_RECORD_TYPES;
   config->stats_path  = "";
   std::string cur_line;
-  IpAddr min, max;
 
   if (!fh) {
     TSDebug(PLUGIN_NAME, "No config file, using defaults");
@@ -787,37 +775,34 @@
   }
 
   while (std::getline(fh, cur_line)) {
-    // skip empty lines
-    if (cur_line == "") {
-      continue;
-    }
-    if (cur_line.at(0) == '#') {
+    swoc::TextView line{cur_line};
+    if (line.ltrim_if(&isspace).empty() || '#' == *line) {
       continue; /* # Comments, only at line beginning */
     }
 
     size_t p = 0;
 
-    if ((p = cur_line.find("path=")) != std::string::npos) {
+    static constexpr swoc::TextView PATH_TAG   = "path=";
+    static constexpr swoc::TextView RECORD_TAG = "record_types=";
+    static constexpr swoc::TextView ADDR_TAG   = "allow_ip=";
+    static constexpr swoc::TextView ADDR6_TAG  = "allow_ip6=";
+
+    if ((p = line.find(PATH_TAG)) != std::string::npos) {
+      line.remove_prefix(p + PATH_TAG.size()).ltrim('/');
       TSDebug(PLUGIN_NAME, "parsing path");
-      p += strlen("path=");
-      if (cur_line.at(p + 1) == '/') {
-        p++;
-      }
-      config->stats_path = cur_line.substr(p);
-    } else if ((p = cur_line.find("record_types=")) != std::string::npos) {
+      config->stats_path = line;
+    } else if ((p = line.find(RECORD_TAG)) != std::string::npos) {
       TSDebug(PLUGIN_NAME, "parsing record types");
-      p += strlen("record_types=");
-      config->recordTypes = strtol(cur_line.substr(p).c_str(), nullptr, 16);
-    } else if ((p = cur_line.find("allow_ip=")) != std::string::npos) {
-      p += strlen("allow_ip=");
-      parseIpMap(config, cur_line.substr(p).c_str());
-    } else if ((p = cur_line.find("allow_ip6=")) != std::string::npos) {
-      p += strlen("allow_ip6=");
-      parseIpMap(config, cur_line.substr(p).c_str());
+      line.remove_prefix(p).remove_prefix(RECORD_TAG.size());
+      config->recordTypes = swoc::svtou(line, nullptr, 16);
+    } else if ((p = line.find(ADDR_TAG)) != std::string::npos) {
+      parseIpMap(config, line.remove_prefix(p).remove_prefix(ADDR_TAG.size()));
+    } else if ((p = line.find(ADDR6_TAG)) != std::string::npos) {
+      parseIpMap(config, line.remove_prefix(p).remove_prefix(ADDR6_TAG.size()));
     }
   }
 
-  if (config->allow_ip_map.count() == 0) {
+  if (config->addrs.count() == 0) {
     TSDebug(PLUGIN_NAME, "empty ip map found, setting defaults");
     parseIpMap(config, nullptr);
   }
diff --git a/plugins/xdebug/xdebug.cc b/plugins/xdebug/xdebug.cc
index c2e4d5f..afb4065 100644
--- a/plugins/xdebug/xdebug.cc
+++ b/plugins/xdebug/xdebug.cc
@@ -28,6 +28,7 @@
 #include <cstdint>
 #include <cinttypes>
 #include <string_view>
+#include <algorithm>
 #include <unistd.h>
 
 #include <ts/ts.h>
@@ -84,6 +85,46 @@
   XHEADER_X_PSELECT_KEY    = 1u << 10,
   XHEADER_X_CACHE_INFO     = 1u << 11,
   XHEADER_X_EFFECTIVE_URL  = 1u << 12,
+  XHEADER_VIA              = 1u << 13,
+  XHEADER_DIAGS            = 1u << 14,
+  XHEADER_ALL              = UINT_MAX
+};
+
+static unsigned int allowedHeaders = 0;
+
+constexpr std::string_view HEADER_NAME_X_CACHE_KEY      = "x-cache-key";
+constexpr std::string_view HEADER_NAME_X_MILESTONES     = "x-milestones";
+constexpr std::string_view HEADER_NAME_X_CACHE          = "x-cache";
+constexpr std::string_view HEADER_NAME_X_GENERATION     = "x-cache-generation";
+constexpr std::string_view HEADER_NAME_X_TRANSACTION_ID = "x-transaction-id";
+constexpr std::string_view HEADER_NAME_X_DUMP_HEADERS   = "x-dump-headers";
+constexpr std::string_view HEADER_NAME_X_REMAP          = "x-remap";
+constexpr std::string_view HEADER_NAME_X_PROBE_HEADERS  = "probe";
+constexpr std::string_view HEADER_NAME_X_PSELECT_KEY    = "x-parentselection-key";
+constexpr std::string_view HEADER_NAME_X_CACHE_INFO     = "x-cache-info";
+constexpr std::string_view HEADER_NAME_X_EFFECTIVE_URL  = "x-effective-url";
+constexpr std::string_view HEADER_NAME_VIA              = "via";
+constexpr std::string_view HEADER_NAME_DIAGS            = "diags";
+constexpr std::string_view HEADER_NAME_ALL              = "all";
+
+constexpr struct XHeader {
+  std::string_view name;
+  unsigned int flag;
+} header_flags[] = {
+  {HEADER_NAME_X_CACHE_KEY, XHEADER_X_CACHE_KEY},
+  {HEADER_NAME_X_MILESTONES, XHEADER_X_MILESTONES},
+  {HEADER_NAME_X_CACHE, XHEADER_X_CACHE},
+  {HEADER_NAME_X_GENERATION, XHEADER_X_GENERATION},
+  {HEADER_NAME_X_TRANSACTION_ID, XHEADER_X_TRANSACTION_ID},
+  {HEADER_NAME_X_DUMP_HEADERS, XHEADER_X_DUMP_HEADERS},
+  {HEADER_NAME_X_REMAP, XHEADER_X_REMAP},
+  {HEADER_NAME_X_PROBE_HEADERS, XHEADER_X_PROBE_HEADERS},
+  {HEADER_NAME_X_PSELECT_KEY, XHEADER_X_PSELECT_KEY},
+  {HEADER_NAME_X_CACHE_INFO, XHEADER_X_CACHE_INFO},
+  {HEADER_NAME_X_EFFECTIVE_URL, XHEADER_X_EFFECTIVE_URL},
+  {HEADER_NAME_VIA, XHEADER_VIA},
+  {HEADER_NAME_DIAGS, XHEADER_DIAGS},
+  {HEADER_NAME_ALL, XHEADER_ALL},
 };
 
 static TSCont XInjectHeadersCont  = nullptr;
@@ -647,30 +688,30 @@
         continue;
       }
 
-#define header_field_eq(name, vptr, vlen) (((int)lengthof(name) == vlen) && (strncasecmp(name, vptr, vlen) == 0))
+#define header_field_eq(name, vptr, vlen) (((int)name.size() == vlen) && (strncasecmp(name.data(), vptr, vlen) == 0))
 
-      if (header_field_eq("x-cache-key", value, vsize)) {
-        xheaders |= XHEADER_X_CACHE_KEY;
-      } else if (header_field_eq("x-cache-info", value, vsize)) {
-        xheaders |= XHEADER_X_CACHE_INFO;
-      } else if (header_field_eq("x-milestones", value, vsize)) {
-        xheaders |= XHEADER_X_MILESTONES;
-      } else if (header_field_eq("x-cache", value, vsize)) {
-        xheaders |= XHEADER_X_CACHE;
-      } else if (header_field_eq("x-cache-generation", value, vsize)) {
-        xheaders |= XHEADER_X_GENERATION;
-      } else if (header_field_eq("x-transaction-id", value, vsize)) {
-        xheaders |= XHEADER_X_TRANSACTION_ID;
-      } else if (header_field_eq("x-remap", value, vsize)) {
-        xheaders |= XHEADER_X_REMAP;
-      } else if (header_field_eq("via", value, vsize)) {
+      if (header_field_eq(HEADER_NAME_X_CACHE_KEY, value, vsize)) {
+        xheaders |= XHEADER_X_CACHE_KEY & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_X_CACHE_INFO, value, vsize)) {
+        xheaders |= XHEADER_X_CACHE_INFO & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_X_MILESTONES, value, vsize)) {
+        xheaders |= XHEADER_X_MILESTONES & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_X_CACHE, value, vsize)) {
+        xheaders |= XHEADER_X_CACHE & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_X_GENERATION, value, vsize)) {
+        xheaders |= XHEADER_X_GENERATION & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_X_TRANSACTION_ID, value, vsize)) {
+        xheaders |= XHEADER_X_TRANSACTION_ID & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_X_REMAP, value, vsize)) {
+        xheaders |= XHEADER_X_REMAP & allowedHeaders;
+      } else if (header_field_eq(HEADER_NAME_VIA, value, vsize) && (XHEADER_VIA & allowedHeaders)) {
         // If the client requests the Via header, enable verbose Via debugging for this transaction.
         TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_INSERT_RESPONSE_VIA_STR, 3);
-      } else if (header_field_eq("diags", value, vsize)) {
+      } else if (header_field_eq(HEADER_NAME_DIAGS, value, vsize) && (XHEADER_DIAGS & allowedHeaders)) {
         // Enable diagnostics for DebugTxn()'s only
         TSHttpTxnCntlSet(txn, TS_HTTP_CNTL_TXN_DEBUG, true);
 
-      } else if (header_field_eq("probe", value, vsize)) {
+      } else if (header_field_eq(HEADER_NAME_X_PROBE_HEADERS, value, vsize) && (XHEADER_X_PROBE_HEADERS & allowedHeaders)) {
         xheaders |= XHEADER_X_PROBE_HEADERS;
 
         auto &auxData = AuxDataMgr::data(txn);
@@ -691,8 +732,8 @@
         TSHttpTxnTransformedRespCache(txn, 0);
         TSHttpTxnUntransformedRespCache(txn, 0);
 
-      } else if (header_field_eq("x-parentselection-key", value, vsize)) {
-        xheaders |= XHEADER_X_PSELECT_KEY;
+      } else if (header_field_eq(HEADER_NAME_X_PSELECT_KEY, value, vsize)) {
+        xheaders |= XHEADER_X_PSELECT_KEY & allowedHeaders;
 
       } else if (isFwdFieldValue(std::string_view(value, vsize), fwdCnt)) {
         if (fwdCnt > 0) {
@@ -701,8 +742,8 @@
           snprintf(newVal, sizeof(newVal), "fwd=%" PRIiMAX, fwdCnt - 1);
           TSMimeHdrFieldValueStringSet(buffer, hdr, field, i, newVal, std::strlen(newVal));
         }
-      } else if (header_field_eq("x-effective-url", value, vsize)) {
-        xheaders |= XHEADER_X_EFFECTIVE_URL;
+      } else if (header_field_eq(HEADER_NAME_X_EFFECTIVE_URL, value, vsize)) {
+        xheaders |= XHEADER_X_EFFECTIVE_URL & allowedHeaders;
       } else {
         TSDebug("xdebug", "ignoring unrecognized debug tag '%.*s'", vsize, value);
       }
@@ -768,10 +809,32 @@
   return TS_EVENT_NONE;
 }
 
+static void
+updateAllowedHeaders(const char *optarg)
+{
+  char *list;
+  char *token;
+  char *last;
+
+  list = TSstrdup(optarg);
+  std::transform(list, list + strlen(list), list, ::tolower);
+  for (token = strtok_r(list, ",", &last); token; token = strtok_r(nullptr, ",", &last)) {
+    const auto ite = std::find_if(std::begin(header_flags), std::end(header_flags),
+                                  [token](const struct XHeader &x) { return x.name.compare(token) == 0; });
+    if (ite != std::end(header_flags)) {
+      allowedHeaders |= ite->flag;
+    } else {
+      TSError("[xdebug] Unknown header name: %s", token);
+    }
+  }
+  TSfree(list);
+}
+
 void
 TSPluginInit(int argc, const char *argv[])
 {
   static const struct option longopt[] = {{const_cast<char *>("header"), required_argument, nullptr, 'h'},
+                                          {const_cast<char *>("enable"), required_argument, nullptr, 'e'},
                                           {nullptr, no_argument, nullptr, '\0'}};
   TSPluginRegistrationInfo info;
 
@@ -791,6 +854,9 @@
     case 'h':
       xDebugHeader.str = TSstrdup(optarg);
       break;
+    case 'e':
+      updateAllowedHeaders(optarg);
+      break;
     }
 
     if (opt == -1) {
@@ -798,6 +864,10 @@
     }
   }
 
+  if (allowedHeaders == 0) {
+    TSError("[xdebug] No features are enabled");
+  }
+
   if (nullptr == xDebugHeader.str) {
     xDebugHeader.str = TSstrdup("X-Debug"); // We malloc this, for consistency for future plugin unload events
   }
diff --git a/proxy/ControlMatcher.cc b/proxy/ControlMatcher.cc
index f5a3c60..3866c8f 100644
--- a/proxy/ControlMatcher.cc
+++ b/proxy/ControlMatcher.cc
@@ -783,7 +783,6 @@
   int line_num       = 0;
   int second_pass    = 0;
   int numEntries     = 0;
-  bool alarmAlready  = false;
 
   // type counts
   int hostDomain = 0;
@@ -817,7 +816,7 @@
         if (config_tags != &socks_server_tags) {
           Result error =
             Result::failure("%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, errptr);
-          SignalError(error.message(), alarmAlready);
+          Error("%s", error.message());
         }
         ats_free(current);
       } else {
@@ -915,7 +914,7 @@
 
     // Check to see if there was an error in creating the NewEntry
     if (error.failed()) {
-      SignalError(error.message(), alarmAlready);
+      Error("%s", error.message());
     }
 
     // Deallocate the parsing structure
diff --git a/proxy/ControlMatcher.h b/proxy/ControlMatcher.h
index 6365ee0..394841f 100644
--- a/proxy/ControlMatcher.h
+++ b/proxy/ControlMatcher.h
@@ -102,14 +102,6 @@
 #include <cctype>
 #endif
 
-#define SignalError(_buf, _already)                         \
-  {                                                         \
-    if (_already == false)                                  \
-      pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, _buf); \
-    _already = true;                                        \
-    Error("%s", _buf);                                      \
-  }
-
 class HostLookup;
 struct HttpApiInfo;
 struct matcher_line;
diff --git a/proxy/HostStatus.h b/proxy/HostStatus.h
index 1fd5098..e09155c 100644
--- a/proxy/HostStatus.h
+++ b/proxy/HostStatus.h
@@ -33,6 +33,8 @@
 #include <ctime>
 #include <string>
 #include <sstream>
+#include "tscore/Filenames.h"
+#include "tscore/I_Layout.h"
 #include "tscore/ink_rwlock.h"
 #include "records/I_RecProcess.h"
 
@@ -89,6 +91,11 @@
   }
 };
 
+struct HostStatuses {
+  std::string hostname;
+  std::string status;
+};
+
 // host status POD
 struct HostStatRec {
   TSHostStatus status;
@@ -168,6 +175,33 @@
   }
 };
 
+struct HostStatusSync : public Continuation {
+  std::string hostRecordsFile;
+
+  void sync_task();
+  void
+  getHostStatusPersistentFilePath()
+  {
+    std::string rundir(RecConfigReadRuntimeDir());
+    hostRecordsFile = Layout::relative_to(rundir, ts::filename::HOST_RECORDS);
+  }
+
+public:
+  HostStatusSync()
+  {
+    getHostStatusPersistentFilePath();
+
+    SET_HANDLER(&HostStatusSync::mainEvent);
+  }
+
+  int
+  mainEvent(int event, Event *e)
+  {
+    sync_task();
+    return EVENT_DONE;
+  }
+};
+
 /**
  * Singleton placeholder for next hop status.
  */
@@ -182,11 +216,16 @@
   }
   void setHostStatus(const std::string_view name, const TSHostStatus status, const unsigned int down_time,
                      const unsigned int reason);
-  HostStatRec *getHostStatus(const std::string_view name);
-  void createHostStat(const std::string_view name, const char *data = nullptr);
-  void loadHostStatusFromStats();
+  void loadFromPersistentStore();
   void loadRecord(std::string_view name, HostStatRec &h);
-  RecErrT getHostStat(std::string &stat_name, char *buf, unsigned int buf_len);
+  HostStatRec *getHostStatus(const std::string_view name);
+  void getAllHostStatuses(std::vector<HostStatuses> &hosts);
+  std::string
+  getHostStatusPersistentFilePath()
+  {
+    std::string rundir(RecConfigReadRuntimeDir());
+    return Layout::relative_to(rundir, ts::filename::HOST_RECORDS);
+  }
 
 private:
   HostStatus();
diff --git a/proxy/IPAllow.cc b/proxy/IPAllow.cc
index c2e0fb6..f0d3bd7 100644
--- a/proxy/IPAllow.cc
+++ b/proxy/IPAllow.cc
@@ -25,84 +25,40 @@
  */
 
 #include <sstream>
+
 #include "IPAllow.h"
-#include "tscore/BufferWriter.h"
-#include "tscore/ts_file.h"
-#include "tscore/ink_memory.h"
 #include "tscore/Filenames.h"
+#include "tscpp/util/ts_errata.h"
+
+#include "swoc/Vectray.h"
+#include "swoc/BufferWriter.h"
+#include "swoc/bwf_std.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/bwf_ip.h"
 
 #include "yaml-cpp/yaml.h"
 
-using ts::TextView;
+using swoc::TextView;
 
-namespace
-{
-void
-SignalError(ts::BufferWriter &w, bool &flag)
-{
-  if (!flag) {
-    flag = true;
-    pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, w.data());
-  }
-  Error("%s", w.data());
-}
+using swoc::BufferWriter;
+using swoc::bwf::Spec;
 
-template <typename... Args>
-void
-ParseError(ts::TextView fmt, Args &&... args)
-{
-  ts::LocalBufferWriter<1024> w;
-  w.printv(fmt, std::forward_as_tuple(args...));
-  w.write('\0');
-  Warning("%s", w.data());
-}
-
-} // namespace
-
-namespace ts
+namespace swoc
 {
 BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, IpAllow const *obj)
+bwformat(BufferWriter &w, Spec const &spec, IpAllow const *obj)
 {
   return w.print("{}[{}]", obj->MODULE_NAME, obj->get_config_file().c_str());
 }
 
+// This needs to be in namespace "swoc" or "YAML" or ADL doesn't find the overload.
 BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, YAML::Mark const &mark)
+bwformat(BufferWriter &w, Spec const &spec, YAML::Mark const &mark)
 {
   return w.print("Line {}", mark.line);
 }
 
-BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, std::error_code const &ec)
-{
-  return w.print("[{}:{}]", ec.value(), ec.message());
-}
-
-} // namespace ts
-
-namespace YAML
-{
-template <> struct convert<ts::TextView> {
-  static Node
-  encode(ts::TextView const &tv)
-  {
-    Node zret;
-    zret = std::string(tv.data(), tv.size());
-    return zret;
-  }
-  static bool
-  decode(const Node &node, ts::TextView &tv)
-  {
-    if (!node.IsScalar()) {
-      return false;
-    }
-    tv.assign(node.Scalar());
-    return true;
-  }
-};
-
-} // namespace YAML
+} // namespace swoc
 
 enum AclOp {
   ACL_OP_ALLOW, ///< Allow access.
@@ -120,6 +76,15 @@
 //
 //   Begin API functions
 //
+swoc::TextView
+IpAllow::localize(swoc::TextView src)
+{
+  auto span = _arena.alloc(src.size() + 1).rebind<char>(); // always make a C-str if copying.
+  memcpy(span.data(), src.data(), src.size());
+  span[src.size()] = '\0';
+  return span.remove_suffix(1); // don't put the extra terminating nul in the view.
+}
+
 void
 IpAllow::startup()
 {
@@ -145,10 +110,12 @@
 
   Note("%s loading ...", ts::filename::IP_ALLOW);
 
-  new_table     = new self_type("proxy.config.cache.ip_allow.filename");
-  int retStatus = new_table->BuildTable();
-  if (retStatus) {
-    Error("%s failed to load", ts::filename::IP_ALLOW);
+  new_table   = new self_type("proxy.config.cache.ip_allow.filename");
+  auto errata = new_table->BuildTable();
+  if (!errata.is_ok()) {
+    std::string text;
+    swoc::bwprint(text, "{} failed to load\n{}", ts::filename::IP_ALLOW, errata);
+    Error("%s", text.c_str());
     delete new_table;
   } else {
     configid = configProcessor.set(configid, new_table);
@@ -175,27 +142,30 @@
 }
 
 IpAllow::ACL
-IpAllow::match(sockaddr const *ip, match_key_t key)
+IpAllow::match(swoc::IPAddr const &addr, match_key_t key)
 {
-  self_type *self = acquire();
-  void *raw       = nullptr;
+  self_type *self      = acquire();
+  Record const *record = nullptr;
   if (SRC_ADDR == key) {
-    self->_src_map.contains(ip, &raw);
-    Record *r = static_cast<Record *>(raw);
-    // Special check - if checking in accept is enabled and the record is a deny all,
-    // then return a missing record instead to force an immediate deny. Otherwise it's delayed
-    // until after remap, to allow remap rules to tweak the result.
-    if (raw && r->_method_mask == 0 && r->_nonstandard_methods.empty() && accept_check_p) {
-      raw = nullptr;
+    if (auto spot = self->_src_map.find(addr); spot != self->_src_map.end()) {
+      auto r = std::get<1>(*spot);
+      // Special case - if checking in accept is enabled and the record is a deny all,
+      // then return a missing record instead to force an immediate deny. Otherwise it's delayed
+      // until after remap, to allow remap rules to tweak the result.
+      if (!(accept_check_p && r->_method_mask == 0 && r->_nonstandard_methods.empty())) {
+        record = r;
+      }
     }
-  } else {
-    self->_dst_map.contains(ip, &raw);
+  } else if (auto spot = self->_dst_map.find(addr); spot != self->_dst_map.end()) {
+    record = std::get<1>(*spot);
   }
-  if (raw == nullptr) {
-    self->release();
-    self = nullptr;
+
+  if (record == nullptr) {
+    self->release(); // no record, don't keep a reference to the config.
+    return {};
   }
-  return ACL{static_cast<Record *>(raw), self};
+
+  return ACL{record, self}; // Note this keeps the config in memory.
 }
 
 //
@@ -204,400 +174,272 @@
 
 IpAllow::IpAllow(const char *config_var) : config_file(ats_scoped_str(RecConfigReadConfigPath(config_var)).get()) {}
 
-void
-IpAllow::PrintMap(const IpMap *map) const
+BufferWriter &
+bwformat(BufferWriter &w, Spec const &spec, IpAllow::IpMap const &map)
 {
-  std::ostringstream s;
-  s << map->count() << " ACL entries.";
-  for (auto &spot : *map) {
-    char text[INET6_ADDRSTRLEN];
-    Record const *ar = static_cast<Record const *>(spot.data());
-
-    s << std::endl << "  Line " << ar->_src_line << ": " << ats_ip_ntop(spot.min(), text, sizeof text);
-    if (0 != ats_ip_addr_cmp(spot.min(), spot.max())) {
-      s << " - " << ats_ip_ntop(spot.max(), text, sizeof text);
-    }
-    s << " method=";
-    uint32_t mask = ALL_METHOD_MASK & ar->_method_mask;
-    if (ALL_METHOD_MASK == mask) {
-      s << "ALL";
+  w.print("{} entries", map.count());
+  for (auto const &spot : map) {
+    auto const *r = std::get<1>(spot);
+    w.print("\n  Line {}: {} methods=", r->_src_line, std::get<0>(spot));
+    uint32_t mask = IpAllow::ALL_METHOD_MASK & r->_method_mask;
+    if (IpAllow::ALL_METHOD_MASK == mask) {
+      w.write("ALL");
     } else if (0 == mask) {
-      s << "NONE";
+      w.write("NONE");
     } else {
       bool leader        = false; // need leading vbar?
       uint32_t test_mask = 1;     // mask for current method.
       for (int i = 0; i < HTTP_WKSIDX_METHODS_CNT; ++i, test_mask <<= 1) {
         if (mask & test_mask) {
-          if (leader) {
-            s << '|';
-          }
-          s << hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT);
+          w.print("{}{}", swoc::bwf::If(leader, "|"), hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT));
           leader = true;
         }
       }
     }
-    if (!ar->_nonstandard_methods.empty()) {
-      s << " other methods=";
+
+    if (!r->_nonstandard_methods.empty()) {
+      w.print(" {}=", r->_deny_nonstandard_methods ? IpAllow::YAML_VALUE_ACTION_ALLOW : IpAllow::YAML_VALUE_ACTION_DENY);
       bool leader = false; // need leading vbar?
-      for (const auto &_nonstandard_method : ar->_nonstandard_methods) {
-        if (leader) {
-          s << '|';
-        }
-        s << _nonstandard_method;
+      for (auto const &name : r->_nonstandard_methods) {
+        w.print("{}{}", swoc::bwf::If(leader, "|"), name);
         leader = true;
       }
     }
   }
-  Debug("ip_allow", "%s", s.str().c_str());
+  return w;
+}
+
+void
+IpAllow::DebugMap(const IpMap &map) const
+{
+  std::string out;
+  out.resize(8192);
+  swoc::bwprint(out, "{}", map);
+  Debug("ip_allow", "%s", out.c_str());
 }
 
 void
 IpAllow::Print() const
 {
   Debug("ip_allow", "Printing src map");
-  PrintMap(&_src_map);
+  DebugMap(_src_map);
   Debug("ip_allow", "Printing dest map");
-  PrintMap(&_dst_map);
+  DebugMap(_dst_map);
 }
 
-int
+swoc::Errata
 IpAllow::BuildTable()
 {
   // Table should be empty
   ink_assert(_src_map.count() == 0 && _dst_map.count() == 0);
 
   std::error_code ec;
-  std::string content{ts::file::load(config_file, ec)};
+  std::string content{swoc::file::load(config_file, ec)};
+  swoc::Errata errata;
   if (ec.value() == 0) {
-    // If it's a .yaml or the root tag is present, treat as YAML.
-    if (TextView{config_file.view()}.take_suffix_at('.') == "yaml" || std::string::npos != content.find(YAML_TAG_ROOT)) {
-      try {
-        this->YAMLBuildTable(content);
-      } catch (std::exception &ex) {
-        ParseError("{} - Invalid config: {}", this, ex.what());
-        return 1;
-      }
-    } else {
-      this->ATSBuildTable(content);
+    try {
+      errata = this->YAMLBuildTable(content);
+    } catch (std::exception &ex) {
+      return swoc::Errata(ec, ERRATA_ERROR, "{} - Invalid config: {}", this, ex.what());
+    }
+    if (!errata.is_ok()) {
+      errata.note("While parsing config file");
+      return errata;
     }
 
     if (_src_map.count() == 0 && _dst_map.count() == 0) {
-      ParseError("{} - No entries found. All IP Addresses will be blocked", this);
-      return 1;
+      return swoc::Errata(ERRATA_ERROR, "{} - No entries found. All IP Addresses will be blocked", this);
     }
 
-    // convert the coloring from indices to pointers.
-    for (auto &item : _src_map) {
-      item.setData(&_src_acls[reinterpret_cast<size_t>(item.data())]);
-    }
-    for (auto &item : _dst_map) {
-      item.setData(&_dst_acls[reinterpret_cast<size_t>(item.data())]);
-    }
     if (is_debug_tag_set("ip_allow")) {
       Print();
     }
   } else {
-    ParseError("{} Failed to load {}. All IP Addresses will be blocked", this, ec);
-    return 1;
+    return swoc::Errata(ERRATA_ERROR, "{} Failed to load {}. All IP Addresses will be blocked", this, ec);
   }
-  return 0;
+  return {};
 }
 
-bool
+swoc::Errata
 IpAllow::YAMLLoadMethod(const YAML::Node &node, Record &rec)
 {
-  const std::string &value{node.Scalar()};
-
-  if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) {
-    rec._method_mask = ALL_METHOD_MASK;
-  } else {
-    int method_idx = hdrtoken_tokenize(value.data(), value.size());
-    if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
-      rec._nonstandard_methods.push_back(value);
-      Debug("ip_allow", "Found nonstandard method '%s' at line %d", value.c_str(), node.Mark().line);
-    } else { // valid method.
-      rec._method_mask |= ACL::MethodIdxToMask(method_idx);
-    }
-  }
-  return true;
-}
-
-bool
-IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, void *mark)
-{
-  if (node.IsScalar()) {
-    IpAddr min, max;
-    if (0 == ats_ip_range_parse(node.Scalar(), min, max)) {
-      map->fill(min, max, mark);
-      return true;
+  swoc::TextView value{node.Scalar()};
+  swoc::Vectray<swoc::TextView, 8> names;
+  // Process a single token. Required to deal with the variable number of tokens.
+  auto parse_method = [&](swoc::TextView value) -> void {
+    if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) {
+      rec._method_mask = ALL_METHOD_MASK;
     } else {
-      ParseError("{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
+      int method_idx = hdrtoken_tokenize(value.data(), value.size());
+      if (HTTP_WKSIDX_CONNECT <= method_idx && method_idx < HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
+        rec._method_mask |= ACL::MethodIdxToMask(method_idx);
+      } else {
+        names.push_back(value);
+        Debug("ip_allow", "Found nonstandard method '%.*s' at line %d", int(value.size()), value.data(), node.Mark().line);
+      }
+    }
+  };
+
+  if (node.IsScalar()) {
+    parse_method(swoc::TextView(node.Scalar()));
+  } else if (node.IsSequence()) {
+    for (auto const &elt : node) {
+      if (elt.IsScalar()) {
+        parse_method(swoc::TextView(elt.Scalar()));
+        if (rec._method_mask == ALL_METHOD_MASK) {
+          break; // we're done here, nothing else matters.
+        }
+      } else {
+        return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(),
+                            YAML_TAG_METHODS);
+      }
+    }
+  } else {
+    return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this,
+                        node.Mark(), YAML_TAG_METHODS);
+  }
+
+  // copy over to local memory if it's not all methods and there are non-standard ones.
+  if (rec._method_mask != ALL_METHOD_MASK && !names.empty()) {
+    rec._nonstandard_methods = _arena.alloc_span<swoc::TextView>(names.size());
+    for (unsigned idx = 0; idx < names.size(); ++idx) {
+      rec._nonstandard_methods[idx] = this->localize(names[idx]);
     }
   }
-  return false;
+  return {};
 }
 
-bool
+swoc::Errata
+IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, IpAllow::Record const *record)
+{
+  if (!node.IsScalar()) {
+    return swoc::Errata(ERRATA_ERROR, "{} Expected IP address range at {}, found non-literal.", this, node.Mark());
+  }
+
+  swoc::TextView debug(node.Scalar());
+  (void)debug;
+  if (swoc::IPRange r; r.load(node.Scalar())) {
+    map->fill(r, record);
+  } else {
+    return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
+  }
+  return {};
+}
+
+swoc::Errata
 IpAllow::YAMLLoadEntry(const YAML::Node &entry)
 {
   AclOp op = ACL_OP_DENY; // "shut up", I explained to the compiler.
   YAML::Node node;
-  IpAddr min, max;
-  std::string value;
-  Record rec;
-  std::vector<Record> *acls{nullptr};
-  IpMap *map = nullptr;
+  auto record = _arena.make<Record>();
+  IpMap *map  = nullptr; // src or dst map.
 
   if (!entry.IsMap()) {
-    ParseError("{} {} - ACL items must be maps.", this, entry.Mark());
-    return false;
+    return swoc::Errata(ERRATA_ERROR, "{} {} - ACL items must be maps.", this, entry.Mark());
   }
 
-  if (entry[YAML_TAG_APPLY]) {
-    auto apply_node{entry[YAML_TAG_APPLY]};
+  if (YAML::Node apply_node{entry[YAML_TAG_APPLY]}; apply_node) {
     if (apply_node.IsScalar()) {
-      ts::TextView value{apply_node.Scalar()};
+      swoc::TextView value{apply_node.Scalar()};
       if (0 == strcasecmp(value, YAML_VALUE_APPLY_IN)) {
-        acls = &_src_acls;
-        map  = &_src_map;
+        map = &_src_map;
       } else if (0 == strcasecmp(value, YAML_VALUE_APPLY_OUT)) {
-        acls = &_dst_acls;
-        map  = &_dst_map;
+        map = &_dst_map;
       } else {
-        ParseError(R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
-                   YAML_VALUE_APPLY_OUT);
-        return false;
+        return swoc::Errata(ERRATA_ERROR, R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(),
+                            YAML_VALUE_APPLY_IN, YAML_VALUE_APPLY_OUT);
       }
     } else {
-      ParseError(R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
-                 YAML_VALUE_APPLY_OUT);
-      return false;
+      return swoc::Errata(ERRATA_ERROR, R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(),
+                          YAML_VALUE_APPLY_IN, YAML_VALUE_APPLY_OUT);
     }
   } else {
-    ParseError(R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY);
-    return false;
+    return swoc::Errata(ERRATA_ERROR, R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY);
   }
 
-  void *ipmap_mark = reinterpret_cast<void *>(acls->size());
-  if (entry[YAML_TAG_IP_ADDRS]) {
-    auto addr_node{entry[YAML_TAG_IP_ADDRS]};
+  if (node = entry[YAML_TAG_ACTION]; node) {
+    if (node.IsScalar()) {
+      swoc::TextView value(node.Scalar());
+      if (value == YAML_VALUE_ACTION_ALLOW) {
+        op = ACL_OP_ALLOW;
+      } else if (value == YAML_VALUE_ACTION_DENY) {
+        op = ACL_OP_DENY;
+      } else {
+        return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(),
+                            YAML_TAG_ACTION, YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY);
+      }
+    } else {
+      return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(),
+                          YAML_TAG_ACTION);
+    }
+  } else {
+    return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION);
+  }
+
+  if (YAML::Node addr_node = entry[YAML_TAG_IP_ADDRS]; addr_node) {
+    bool marked_p = false;
     if (addr_node.IsSequence()) {
       for (auto const &n : addr_node) {
-        if (!this->YAMLLoadIPAddrRange(n, map, ipmap_mark)) {
-          return false;
-        }
-      }
-    } else if (!this->YAMLLoadIPAddrRange(addr_node, map, ipmap_mark)) {
-      return false;
-    }
-  }
-
-  if (!entry[YAML_TAG_ACTION]) {
-    ParseError("{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION);
-    return false;
-  }
-
-  node = entry[YAML_TAG_ACTION];
-  if (!node.IsScalar()) {
-    ParseError("{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(), YAML_TAG_ACTION);
-    return false;
-  }
-  value = node.as<std::string>();
-  if (value == YAML_VALUE_ACTION_ALLOW) {
-    op = ACL_OP_ALLOW;
-  } else if (value == YAML_VALUE_ACTION_DENY) {
-    op = ACL_OP_DENY;
-  } else {
-    ParseError("{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(), YAML_TAG_ACTION,
-               YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY);
-    return false;
-  }
-  if (!entry[YAML_TAG_METHODS]) {
-    rec._method_mask = ALL_METHOD_MASK;
-  } else {
-    node = entry[YAML_TAG_METHODS];
-    if (node.IsScalar()) {
-      this->YAMLLoadMethod(node, rec);
-    } else if (node.IsSequence()) {
-      for (auto const &elt : node) {
-        if (elt.IsScalar()) {
-          this->YAMLLoadMethod(elt, rec);
-          if (rec._method_mask == ALL_METHOD_MASK) {
-            break; // we're done here, nothing else matters.
-          }
+        if (auto errata = this->YAMLLoadIPAddrRange(n, map, record); errata.is_ok()) {
+          marked_p = true;
         } else {
-          ParseError("{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(), YAML_TAG_METHODS);
-          return false;
+          errata.note(R"(In record at {})", entry.Mark());
+          return errata;
         }
       }
     } else {
-      ParseError("{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this, node.Mark(),
-                 YAML_TAG_METHODS);
+      if (auto errata = this->YAMLLoadIPAddrRange(addr_node, map, record); errata.is_ok()) {
+        marked_p = true;
+      } else {
+        errata.note(R"(In record at {})", entry.Mark());
+        return errata;
+      }
     }
+    if (!marked_p) {
+      return swoc::Errata(ERRATA_ERROR, "No valid addresses for rule at {}", node.Mark());
+    }
+  } else {
+    return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_IP_ADDRS);
   }
+
+  if (node = entry[YAML_TAG_METHODS]; node) {
+    if (auto errata = this->YAMLLoadMethod(node, *record); !errata.is_ok()) {
+      return errata;
+    }
+  } else {
+    record->_method_mask = ALL_METHOD_MASK;
+  }
+
   if (op == ACL_OP_DENY) {
-    rec._method_mask              = ALL_METHOD_MASK & ~rec._method_mask;
-    rec._deny_nonstandard_methods = true;
+    record->_method_mask              = ALL_METHOD_MASK & ~record->_method_mask;
+    record->_deny_nonstandard_methods = true;
   }
-  rec._src_line = entry.Mark().line;
-  // If we get here, everything parsed OK, add the record.
-  acls->emplace_back(std::move(rec));
-  return true;
+
+  record->_src_line = entry.Mark().line;
+  return {};
 }
 
-int
+swoc::Errata
 IpAllow::YAMLBuildTable(std::string const &content)
 {
   YAML::Node root{YAML::Load(content)};
   if (!root.IsMap()) {
-    ParseError("{} - top level object was not a map. All IP Addresses will be blocked", this);
-    return 1;
+    return swoc::Errata("{} - top level object was not a map. All IP Addresses will be blocked", this);
   }
 
-  YAML::Node data{root[YAML_TAG_ROOT]};
+  YAML::Node data{root[YAML_TAG_ROOT.data()]};
   if (!data) {
-    ParseError("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
+    return swoc::Errata("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
   } else if (data.IsSequence()) {
     for (auto const &entry : data) {
-      if (!this->YAMLLoadEntry(entry)) {
-        return 1;
+      if (auto errata = this->YAMLLoadEntry(entry); !errata.is_ok()) {
+        return errata;
       }
     }
   } else if (data.IsMap()) {
-    this->YAMLLoadEntry(data); // singleton, just load it.
+    return this->YAMLLoadEntry(data); // singleton, just load it.
   } else {
-    ParseError("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
-    return 1;
+    return swoc::Errata("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
   }
-  return 0;
-}
-
-int
-IpAllow::ATSBuildTable(std::string const &content)
-{
-  int line_num = 0;
-  IpAddr addr1;
-  IpAddr addr2;
-  bool alarmAlready = false;
-  ts::LocalBufferWriter<1024> bw_err;
-
-  TextView src(content);
-  TextView line;
-  auto err_prefix = [&]() -> ts::BufferWriter & {
-    return bw_err.reset().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file.c_str(), line_num);
-  };
-
-  while (!(line = src.take_prefix_at('\n')).empty()) {
-    ++line_num;
-    line.trim_if(&isspace);
-
-    if (!line.empty() && *line != '#') {
-      TextView token = line.take_prefix_if(&isspace);
-      TextView value = token.split_suffix_at('=');
-      match_key_t match;
-      if (value.empty()) {
-        err_prefix().print("No value found in token '{}'.\0", token);
-        SignalError(bw_err, alarmAlready);
-        continue;
-      } else if (strcasecmp(token, OPT_MATCH_SRC) == 0) {
-        match = SRC_ADDR;
-      } else if (strcasecmp(token, OPT_MATCH_DST) == 0) {
-        match = DST_ADDR;
-      } else {
-        err_prefix().print("'{}' is not a valid key.\0", token);
-        SignalError(bw_err, alarmAlready);
-        continue;
-      }
-
-      if (0 == ats_ip_range_parse(value, addr1, addr2)) {
-        uint32_t acl_method_mask      = 0;
-        bool op_found_p               = false;
-        bool method_found_p           = false;
-        bool all_found_p              = false;
-        bool deny_nonstandard_methods = false;
-        bool line_valid_p             = true;
-        AclOp op                      = ACL_OP_DENY; // "shut up", I explained to the compiler.
-        MethodNames nonstandard_methods;
-
-        while (line_valid_p && !line.ltrim_if(&isspace).empty()) {
-          token = line.take_prefix_if(&isspace);
-          value = token.split_suffix_at('=');
-
-          if (value.empty()) {
-            err_prefix().print("No value found in token '{}'\0", token);
-            SignalError(bw_err, alarmAlready);
-            line_valid_p = false;
-          } else if (strcasecmp(token, OPT_ACTION_TAG) == 0) {
-            if (strcasecmp(value, OPT_ACTION_ALLOW) == 0) {
-              op_found_p = true, op = ACL_OP_ALLOW;
-            } else if (strcasecmp(value, OPT_ACTION_DENY) == 0) {
-              op_found_p = true, op = ACL_OP_DENY;
-            } else {
-              err_prefix().print("'{}' is not a valid action\0", value);
-              SignalError(bw_err, alarmAlready);
-              line_valid_p = false;
-            }
-          } else if (strcasecmp(token, OPT_METHOD) == 0) {
-            // Parse method="GET|HEAD"
-            while (!value.empty()) {
-              TextView method_name = value.take_prefix_at('|');
-              if (strcasecmp(method_name, OPT_METHOD_ALL) == 0) {
-                all_found_p = true;
-                break;
-              } else {
-                int method_idx = hdrtoken_tokenize(method_name.data(), method_name.size());
-                if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
-                  nonstandard_methods.emplace_back(std::string(method_name.data(), method_name.size()));
-                  Debug("ip_allow", "%s",
-                        bw_err.reset().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data());
-                } else { // valid method.
-                  acl_method_mask |= ACL::MethodIdxToMask(method_idx);
-                }
-                method_found_p = true;
-              }
-            }
-          } else {
-            err_prefix().print("'{}' is not a valid token\0", token);
-            SignalError(bw_err, alarmAlready);
-            line_valid_p = false;
-          }
-        }
-        if (!line_valid_p) {
-          continue; // error parsing the line, go on to the next.
-        }
-        if (!op_found_p) {
-          err_prefix().print("No action found.\0");
-          SignalError(bw_err, alarmAlready);
-          continue;
-        }
-        // If method not specified, default to ALL
-        if (all_found_p || !method_found_p) {
-          method_found_p  = true;
-          acl_method_mask = ALL_METHOD_MASK;
-          nonstandard_methods.clear();
-        }
-        // When deny, use bitwise complement.  (Make the rule 'allow for all
-        // methods except those specified')
-        if (op == ACL_OP_DENY) {
-          acl_method_mask          = ALL_METHOD_MASK & ~acl_method_mask;
-          deny_nonstandard_methods = true;
-        }
-
-        if (method_found_p) {
-          std::vector<Record> &acls = match == DST_ADDR ? _dst_acls : _src_acls;
-          IpMap &map                = match == DST_ADDR ? _dst_map : _src_map;
-          acls.emplace_back(acl_method_mask, line_num, std::move(nonstandard_methods), deny_nonstandard_methods);
-          // Color with index in acls because at this point the address is volatile.
-          map.fill(addr1, addr2, reinterpret_cast<void *>(acls.size() - 1));
-        } else {
-          err_prefix().print("No valid method found\0"); // changed by YTS Team, yamsat bug id -59022
-          SignalError(bw_err, alarmAlready);
-        }
-      } else {
-        err_prefix().print("'{}' is not a valid IP address range\0", value);
-        SignalError(bw_err, alarmAlready);
-      }
-    }
-  }
-  return 0;
+  return {};
 }
diff --git a/proxy/IPAllow.h b/proxy/IPAllow.h
index 9221e75..4dd9053 100644
--- a/proxy/IPAllow.h
+++ b/proxy/IPAllow.h
@@ -36,9 +36,10 @@
 
 #include "hdrs/HTTP.h"
 #include "ProxyConfig.h"
-#include "tscore/IpMap.h"
-#include "tscpp/util/TextView.h"
-#include "tscore/ts_file.h"
+#include "swoc/TextView.h"
+#include "swoc/swoc_file.h"
+#include "swoc/swoc_ip.h"
+#include "swoc/Errata.h"
 
 // forward declare in name only so it can be a friend.
 struct IpAllowUpdate;
@@ -53,7 +54,7 @@
 {
   friend struct IpAllowUpdate;
 
-  using MethodNames = std::vector<std::string>;
+  using MethodNames = swoc::MemSpan<swoc::TextView>;
 
   static constexpr uint32_t ALL_METHOD_MASK = ~0; // Mask for all methods.
 
@@ -65,40 +66,54 @@
     /// Present only to make Vec<> happy, do not use.
     Record()              = default;
     Record(Record &&that) = default;
+    /** Construct from mask.
+     *
+     * @param method_mask Bit mask of allowed methods.
+     */
     explicit Record(uint32_t method_mask);
+
+    /** Construct from values.
+     *
+     * @param method_mask Well known method mask.
+     * @param line Source line in configuration file.
+     * @param nonstandard_methods Allowed methods that are not well known.
+     * @param deny_nonstandard_methods Denied methods that are not well known.
+     */
     Record(uint32_t method_mask, int line, MethodNames &&nonstandard_methods, bool deny_nonstandard_methods);
 
-    uint32_t _method_mask{0};
-    int _src_line{0};
-    MethodNames _nonstandard_methods;
-    bool _deny_nonstandard_methods{false};
+    uint32_t _method_mask{0};              ///< Well known method mask.
+    int _src_line{0};                      ///< Configuration file sourc line.
+    MethodNames _nonstandard_methods;      ///< Allowed methods that are not well known.
+    bool _deny_nonstandard_methods{false}; ///< Denied methods that are not well known.
   };
 
 public:
   using self_type     = IpAllow; ///< Self reference type.
   using scoped_config = ConfigProcessor::scoped_config<self_type, self_type>;
+  using IpMap         = swoc::IPSpace<Record const *>;
 
   // indicator for whether we should be checking the acl record for src ip or dest ip
   enum match_key_t { SRC_ADDR, DST_ADDR };
-  /// Token strings for configuration
-  static constexpr ts::TextView OPT_MATCH_SRC{"src_ip"};
-  static constexpr ts::TextView OPT_MATCH_DST{"dest_ip"};
-  static constexpr ts::TextView OPT_ACTION_TAG{"action"};
-  static constexpr ts::TextView OPT_ACTION_ALLOW{"ip_allow"};
-  static constexpr ts::TextView OPT_ACTION_DENY{"ip_deny"};
-  static constexpr ts::TextView OPT_METHOD{"method"};
-  static constexpr ts::TextView OPT_METHOD_ALL{"all"};
 
-  static constexpr ts::TextView YAML_TAG_ROOT{"ip_allow"};
-  static constexpr ts::TextView YAML_TAG_IP_ADDRS{"ip_addrs"};
-  static constexpr ts::TextView YAML_TAG_APPLY{"apply"};
-  static constexpr ts::TextView YAML_VALUE_APPLY_IN{"in"};
-  static constexpr ts::TextView YAML_VALUE_APPLY_OUT{"out"};
-  static constexpr ts::TextView YAML_TAG_ACTION{"action"};
-  static constexpr ts::TextView YAML_VALUE_ACTION_ALLOW{"allow"};
-  static constexpr ts::TextView YAML_VALUE_ACTION_DENY{"deny"};
-  static constexpr ts::TextView YAML_TAG_METHODS{"methods"};
-  static constexpr ts::TextView YAML_VALUE_METHODS_ALL{"all"};
+  /// Token strings for configuration
+  static constexpr swoc::TextView OPT_MATCH_SRC{"src_ip"};
+  static constexpr swoc::TextView OPT_MATCH_DST{"dest_ip"};
+  static constexpr swoc::TextView OPT_ACTION_TAG{"action"};
+  static constexpr swoc::TextView OPT_ACTION_ALLOW{"ip_allow"};
+  static constexpr swoc::TextView OPT_ACTION_DENY{"ip_deny"};
+  static constexpr swoc::TextView OPT_METHOD{"method"};
+  static constexpr swoc::TextView OPT_METHOD_ALL{"all"};
+
+  static const inline std::string YAML_TAG_ROOT{"ip_allow"};
+  static const inline std::string YAML_TAG_IP_ADDRS{"ip_addrs"};
+  static const inline std::string YAML_TAG_APPLY{"apply"};
+  static const inline std::string YAML_VALUE_APPLY_IN{"in"};
+  static const inline std::string YAML_VALUE_APPLY_OUT{"out"};
+  static const inline std::string YAML_TAG_ACTION{"action"};
+  static const inline std::string YAML_VALUE_ACTION_ALLOW{"allow"};
+  static const inline std::string YAML_VALUE_ACTION_DENY{"deny"};
+  static const inline std::string YAML_TAG_METHODS{"methods"};
+  static const inline std::string YAML_VALUE_METHODS_ALL{"all"};
 
   static constexpr const char *MODULE_NAME = "IPAllow";
 
@@ -121,6 +136,11 @@
 
     void clear(); ///< Drop data and config reference.
 
+    /** Convert well known string index to mask.
+     *
+     * @param wksidx Well known string index.
+     * @return A mask for that method.
+     */
     static uint32_t MethodIdxToMask(int wksidx);
 
     /// Check if the ACL is valid (i.e. not uninitialized or missing).
@@ -149,8 +169,9 @@
 
   void Print() const;
 
-  static ACL match(sockaddr const *ip, match_key_t key);
-  static ACL match(IpEndpoint const *ip, match_key_t key);
+  static ACL match(swoc::IPAddr const &addr, match_key_t key);
+  static ACL match(swoc::IPEndpoint const *addr, match_key_t key);
+  static ACL match(sockaddr const *sa, match_key_t key);
 
   static void startup();
   static void reconfigure();
@@ -180,27 +201,31 @@
    */
   static bool isAcceptCheckEnabled();
 
-  const ts::file::path &get_config_file() const;
+  const swoc::file::path &get_config_file() const;
 
 private:
   static size_t configid;               ///< Configuration ID for update management.
   static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access.
   static bool accept_check_p;           ///< @c true if deny all can be enforced during accept.
 
-  void PrintMap(const IpMap *map) const;
+  void DebugMap(IpMap const &map) const;
 
-  int BuildTable();
-  int ATSBuildTable(const std::string &);
-  int YAMLBuildTable(const std::string &);
-  bool YAMLLoadEntry(const YAML::Node &);
-  bool YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, void *mark);
-  bool YAMLLoadMethod(const YAML::Node &node, Record &rec);
+  swoc::Errata BuildTable();
+  swoc::Errata YAMLBuildTable(const std::string &);
+  swoc::Errata YAMLLoadEntry(const YAML::Node &);
+  swoc::Errata YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, Record const *mark);
+  swoc::Errata YAMLLoadMethod(const YAML::Node &node, Record &rec);
 
-  ts::file::path config_file; ///< Path to configuration file.
+  /// Copy @a src to the local arena and review a view of the copy.
+  swoc::TextView localize(swoc::TextView src);
+
+  swoc::file::path config_file; ///< Path to configuration file.
   IpMap _src_map;
   IpMap _dst_map;
-  std::vector<Record> _src_acls;
-  std::vector<Record> _dst_acls;
+  /// Storage for records.
+  swoc::MemArena _arena;
+
+  friend swoc::BufferWriter &bwformat(swoc::BufferWriter &w, swoc::bwf::Spec const &spec, IpAllow::IpMap const &map);
 };
 
 // ------ Record methods --------
@@ -321,9 +346,15 @@
 }
 
 inline auto
-IpAllow::match(IpEndpoint const *ip, match_key_t key) -> ACL
+IpAllow::match(swoc::IPEndpoint const *addr, match_key_t key) -> ACL
 {
-  return self_type::match(&ip->sa, key);
+  return self_type::match(swoc::IPAddr(&addr->sa), key);
+}
+
+inline auto
+IpAllow::match(sockaddr const *sa, match_key_t key) -> ACL
+{
+  return self_type::match(swoc::IPAddr(sa), key);
 }
 
 inline auto
@@ -332,7 +363,7 @@
   return {&ALLOW_ALL_RECORD, nullptr};
 }
 
-inline const ts::file::path &
+inline const swoc::file::path &
 IpAllow::get_config_file() const
 {
   return config_file;
diff --git a/proxy/Makefile.am b/proxy/Makefile.am
index 2c73d31..cd06f2f 100644
--- a/proxy/Makefile.am
+++ b/proxy/Makefile.am
@@ -39,6 +39,7 @@
 	-I$(abs_srcdir)/hdrs \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
+	@SWOC_INCLUDES@ \
 	@YAMLCPP_INCLUDES@ \
 	$(TS_INCLUDES)
 
diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc
index 9d5546e..cc0e4b2 100644
--- a/proxy/ParentSelection.cc
+++ b/proxy/ParentSelection.cc
@@ -598,7 +598,6 @@
 {
   const char *errPtr;
   char *errBuf;
-  bool alarmAlready = false;
 
   this->go_direct       = true;
   this->ignore_query    = false;
@@ -609,7 +608,7 @@
   if (errPtr != nullptr) {
     errBuf = static_cast<char *>(ats_malloc(1024));
     snprintf(errBuf, 1024, "%s %s for default parent proxy", modulePrefix, errPtr);
-    SignalError(errBuf, alarmAlready);
+    Error("%s", errBuf);
     ats_free(errBuf);
     return false;
   } else {
diff --git a/proxy/ProxySession.cc b/proxy/ProxySession.cc
index f4c0585..a4feee9 100644
--- a/proxy/ProxySession.cc
+++ b/proxy/ProxySession.cc
@@ -26,6 +26,8 @@
 #include "ProxySession.h"
 #include "TLSBasicSupport.h"
 
+std::map<int, std::function<PoolableSession *()>> ProtocolSessionCreateMap;
+
 ProxySession::ProxySession() : VConnection(nullptr) {}
 
 ProxySession::ProxySession(NetVConnection *vc) : VConnection(nullptr), _vc(vc) {}
@@ -77,7 +79,8 @@
   TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE, // TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK
   TS_EVENT_HTTP_PRE_REMAP,             // TS_HTTP_PRE_REMAP_HOOK
   TS_EVENT_HTTP_POST_REMAP,            // TS_HTTP_POST_REMAP_HOOK
-  TS_EVENT_NONE,                       // TS_HTTP_RESPONSE_CLIENT_HOOK
+  TS_EVENT_HTTP_RESPONSE_CLIENT,       // TS_HTTP_RESPONSE_CLIENT_HOOK
+  TS_EVENT_HTTP_REQUEST_CLIENT,        // TS_HTTP_REQUEST_CLIENT_HOOK
   TS_EVENT_NONE,                       // TS_HTTP_LAST_HOOK
 };
 
@@ -320,3 +323,11 @@
 {
   return _vc ? _vc->support_sni() : false;
 }
+
+PoolableSession *
+ProxySession::create_outbound_session(int protocol_index)
+{
+  auto iter = ProtocolSessionCreateMap.find(protocol_index);
+  ink_release_assert(iter != ProtocolSessionCreateMap.end());
+  return iter->second();
+}
diff --git a/proxy/ProxySession.h b/proxy/ProxySession.h
index d572e77..941cc05 100644
--- a/proxy/ProxySession.h
+++ b/proxy/ProxySession.h
@@ -162,6 +162,16 @@
     return nullptr;
   }
 
+  /** Given the ALPN protocol index, create an appropriate outbound session.
+   *
+   * @param[in] protocol_index A TS_ALPN_PROTOCOL value indicating what kind of
+   * protocol was negotiated toward the origin.
+   *
+   * @return A poolable session appropriate for the protocol provided via @a
+   * protocol_index.
+   */
+  static PoolableSession *create_outbound_session(int protocol_index);
+
   ////////////////////
   // Members
 
diff --git a/proxy/hdrs/HTTP.cc b/proxy/hdrs/HTTP.cc
index e81136e..c5354bf 100644
--- a/proxy/hdrs/HTTP.cc
+++ b/proxy/hdrs/HTTP.cc
@@ -2068,7 +2068,7 @@
   }
 }
 
-const int HTTP_ALT_MARSHAL_SIZE = HdrHeapMarshalBlocks{ts::round_up(sizeof(HTTPCacheAlt))};
+const int HTTP_ALT_MARSHAL_SIZE = HdrHeapMarshalBlocks{swoc::round_up(sizeof(HTTPCacheAlt))};
 
 void
 HTTPInfo::create()
diff --git a/proxy/hdrs/HdrHeap.cc b/proxy/hdrs/HdrHeap.cc
index 5273b22..59139c3 100644
--- a/proxy/hdrs/HdrHeap.cc
+++ b/proxy/hdrs/HdrHeap.cc
@@ -137,7 +137,7 @@
     alloc_size = HdrStrHeap::DEFAULT_SIZE;
     sh         = static_cast<HdrStrHeap *>(THREAD_ALLOC(strHeapAllocator, this_ethread()));
   } else {
-    alloc_size = ts::round_up<HdrStrHeap::DEFAULT_SIZE * 2>(alloc_size);
+    alloc_size = swoc::round_up<HdrStrHeap::DEFAULT_SIZE * 2>(alloc_size);
     sh         = static_cast<HdrStrHeap *>(ats_malloc(alloc_size));
   }
 
@@ -184,7 +184,7 @@
 
   ink_assert(m_writeable);
 
-  nbytes = HdrHeapMarshalBlocks{ts::round_up(nbytes)};
+  nbytes = HdrHeapMarshalBlocks{swoc::round_up(nbytes)};
 
   if (nbytes > static_cast<int>(HDR_MAX_ALLOC_SIZE)) {
     ink_assert(!"alloc too big");
@@ -580,7 +580,7 @@
     }
   }
 
-  len = HdrHeapMarshalBlocks(ts::round_up(len));
+  len = HdrHeapMarshalBlocks(swoc::round_up(len));
   return len;
 }
 
@@ -802,7 +802,7 @@
 
   // Add up the total bytes used
   used = ptr_heap_size + str_size + HDR_HEAP_HDR_SIZE;
-  used = HdrHeapMarshalBlocks(ts::round_up(used));
+  used = HdrHeapMarshalBlocks(swoc::round_up(used));
 
 #ifdef HDR_HEAP_CHECKSUMS
   {
@@ -976,7 +976,7 @@
 
   m_magic = HDR_BUF_MAGIC_ALIVE;
 
-  unmarshal_size = HdrHeapMarshalBlocks(ts::round_up(unmarshal_size));
+  unmarshal_size = HdrHeapMarshalBlocks(swoc::round_up(unmarshal_size));
   return unmarshal_size;
 }
 
diff --git a/proxy/hdrs/HdrHeap.h b/proxy/hdrs/HdrHeap.h
index 91c7a76..6422aa5 100644
--- a/proxy/hdrs/HdrHeap.h
+++ b/proxy/hdrs/HdrHeap.h
@@ -34,7 +34,7 @@
 
 #include "tscore/Ptr.h"
 #include "tscore/ink_assert.h"
-#include "tscore/Scalar.h"
+#include "swoc/Scalar.h"
 #include "HdrToken.h"
 
 // Objects in the heap must currently be aligned to 8 byte boundaries,
@@ -42,7 +42,7 @@
 
 static constexpr size_t HDR_PTR_SIZE           = sizeof(uint64_t);
 static constexpr size_t HDR_PTR_ALIGNMENT_MASK = HDR_PTR_SIZE - 1L;
-using HdrHeapMarshalBlocks                     = ts::Scalar<HDR_PTR_SIZE>;
+using HdrHeapMarshalBlocks                     = swoc::Scalar<HDR_PTR_SIZE>;
 
 // A many of the operations regarding read-only str
 //  heaps are hand unrolled in the code.  Changing
@@ -319,7 +319,7 @@
   int m_lost_string_space;
 };
 
-static constexpr HdrHeapMarshalBlocks HDR_HEAP_HDR_SIZE{ts::round_up(sizeof(HdrHeap))};
+static constexpr HdrHeapMarshalBlocks HDR_HEAP_HDR_SIZE{swoc::round_up(sizeof(HdrHeap))};
 static constexpr size_t HDR_MAX_ALLOC_SIZE = HdrHeap::DEFAULT_SIZE - HDR_HEAP_HDR_SIZE;
 
 inline void
diff --git a/proxy/hdrs/HdrTSOnly.cc b/proxy/hdrs/HdrTSOnly.cc
index 3a32c3b..5acf807 100644
--- a/proxy/hdrs/HdrTSOnly.cc
+++ b/proxy/hdrs/HdrTSOnly.cc
@@ -28,11 +28,10 @@
    Description:
       IRIX compiler is rather annoying and likes to use symbols from inline
         code that is not called in the file.  Thus to make our libhdrs.a
-        library link with traffic_manager and test_header, we can't
-        include IOBuffer.h in any file where the corresponding object file
-        gets linked in for these two targets.  Thus HdrTSOnly.cc is where
-        we put the functions that only traffic_server uses since they
-        need to know about IOBuffers.
+        library link with test_header, we can't include IOBuffer.h in any
+        file where the corresponding object file gets linked in for these
+        two targets.  Thus HdrTSOnly.cc is where we put the functions that
+        only traffic_server uses since they need to know about IOBuffers.
 
 
  ****************************************************************************/
diff --git a/proxy/hdrs/Makefile.am b/proxy/hdrs/Makefile.am
index d815e0d..1bc80cc 100644
--- a/proxy/hdrs/Makefile.am
+++ b/proxy/hdrs/Makefile.am
@@ -22,6 +22,7 @@
 	$(iocore_include_dirs) \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES)
 
 noinst_LIBRARIES = libhdrs.a
@@ -58,6 +59,7 @@
 	load_http_hdr.cc
 
 load_http_hdr_LDADD = -L. -lhdrs \
+        @SWOC_LIBS@ \
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la
 
@@ -88,7 +90,7 @@
 	$(top_builddir)/src/records/librecords_p.a \
 	$(top_builddir)/mgmt/libmgmt_p.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ @HWLOC_LIBS@ \
 	@LIBCAP@
 
 test_hdr_heap_CPPFLAGS = $(AM_CPPFLAGS) \
@@ -107,7 +109,7 @@
 	$(top_builddir)/src/records/librecords_p.a \
 	$(top_builddir)/mgmt/libmgmt_p.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ @HWLOC_LIBS@ \
 	@LIBCAP@
 
 test_Huffmancode_LDADD = \
diff --git a/proxy/http/Http1ServerSession.cc b/proxy/http/Http1ServerSession.cc
index e0a9c82..8d8ed82 100644
--- a/proxy/http/Http1ServerSession.cc
+++ b/proxy/http/Http1ServerSession.cc
@@ -256,3 +256,7 @@
   trans.set_reader(this->get_remote_reader());
   return &trans;
 }
+
+std::function<PoolableSession *()> create_h1_server_session = []() -> PoolableSession * {
+  return httpServerSessionAllocator.alloc();
+};
diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc
index ebd745f..377985e 100644
--- a/proxy/http/HttpConfig.cc
+++ b/proxy/http/HttpConfig.cc
@@ -28,7 +28,6 @@
 #include <cstring>
 #include "HttpConfig.h"
 #include "HTTP.h"
-#include "ProcessManager.h"
 #include "ProxyConfig.h"
 #include "P_Net.h"
 #include "records/P_RecUtils.h"
@@ -1256,13 +1255,11 @@
 
   HttpEstablishStaticConfigLongLong(c.oride.connect_attempts_rr_retries, "proxy.config.http.connect_attempts_rr_retries");
   HttpEstablishStaticConfigLongLong(c.oride.connect_attempts_timeout, "proxy.config.http.connect_attempts_timeout");
-  HttpEstablishStaticConfigLongLong(c.oride.post_connect_attempts_timeout, "proxy.config.http.post_connect_attempts_timeout");
   HttpEstablishStaticConfigLongLong(c.oride.parent_connect_attempts, "proxy.config.http.parent_proxy.total_connect_attempts");
   HttpEstablishStaticConfigLongLong(c.oride.parent_retry_time, "proxy.config.http.parent_proxy.retry_time");
   HttpEstablishStaticConfigLongLong(c.oride.parent_fail_threshold, "proxy.config.http.parent_proxy.fail_threshold");
   HttpEstablishStaticConfigLongLong(c.oride.per_parent_connect_attempts,
                                     "proxy.config.http.parent_proxy.per_parent_connect_attempts");
-  HttpEstablishStaticConfigLongLong(c.oride.parent_connect_timeout, "proxy.config.http.parent_proxy.connect_attempts_timeout");
   HttpEstablishStaticConfigByte(c.oride.parent_failures_update_hostdb, "proxy.config.http.parent_proxy.mark_down_hostdb");
   HttpEstablishStaticConfigByte(c.oride.enable_parent_timeout_markdowns,
                                 "proxy.config.http.parent_proxy.enable_parent_timeout_markdowns");
@@ -1345,8 +1342,6 @@
   HttpEstablishStaticConfigByte(c.send_100_continue_response, "proxy.config.http.send_100_continue_response");
   HttpEstablishStaticConfigByte(c.disallow_post_100_continue, "proxy.config.http.disallow_post_100_continue");
 
-  HttpEstablishStaticConfigByte(c.keepalive_internal_vc, "proxy.config.http.keepalive_internal_vc");
-
   HttpEstablishStaticConfigByte(c.oride.cache_open_write_fail_action, "proxy.config.http.cache.open_write_fail_action");
 
   HttpEstablishStaticConfigByte(c.oride.cache_when_to_revalidate, "proxy.config.http.cache.when_to_revalidate");
@@ -1421,6 +1416,7 @@
   HttpEstablishStaticConfigByte(c.http_host_sni_policy, "proxy.config.http.host_sni_policy");
 
   HttpEstablishStaticConfigStringAlloc(c.oride.ssl_client_sni_policy, "proxy.config.ssl.client.sni_policy");
+  HttpEstablishStaticConfigStringAlloc(c.oride.ssl_client_alpn_protocols, "proxy.config.ssl.client.alpn_protocols");
   HttpEstablishStaticConfigByte(c.scheme_proto_mismatch_policy, "proxy.config.ssl.client.scheme_proto_mismatch_policy");
 
   OutboundConnTrack::config_init(&c.global_outbound_conntrack, &c.oride.outbound_conntrack);
@@ -1466,19 +1462,10 @@
   params->disable_ssl_parenting        = INT_TO_BOOL(m_master.disable_ssl_parenting);
   params->oride.forward_connect_method = INT_TO_BOOL(m_master.oride.forward_connect_method);
 
-  params->server_max_connections    = m_master.server_max_connections;
-  params->max_websocket_connections = m_master.max_websocket_connections;
-  params->oride.outbound_conntrack  = m_master.oride.outbound_conntrack;
-  params->global_outbound_conntrack = m_master.global_outbound_conntrack;
-
-  // If queuing for outbound connection tracking is enabled without enabling max connections, it is meaningless, so we'll warn
-  if (params->global_outbound_conntrack.queue_size > 0 &&
-      !(params->oride.outbound_conntrack.max > 0 || params->oride.outbound_conntrack.min > 0)) {
-    Warning("'%s' is set, but neither '%s' nor '%s' are "
-            "set, please correct your %s",
-            OutboundConnTrack::CONFIG_VAR_QUEUE_SIZE.data(), OutboundConnTrack::CONFIG_VAR_MAX.data(),
-            OutboundConnTrack::CONFIG_VAR_MIN.data(), ts::filename::RECORDS);
-  }
+  params->server_max_connections                = m_master.server_max_connections;
+  params->max_websocket_connections             = m_master.max_websocket_connections;
+  params->oride.outbound_conntrack              = m_master.oride.outbound_conntrack;
+  params->global_outbound_conntrack             = m_master.global_outbound_conntrack;
   params->oride.attach_server_session_to_client = m_master.oride.attach_server_session_to_client;
   params->oride.max_proxy_cycles                = m_master.oride.max_proxy_cycles;
   params->oride.tunnel_activity_check_period    = m_master.oride.tunnel_activity_check_period;
@@ -1560,12 +1547,10 @@
   params->oride.connect_attempts_rr_retries     = m_master.oride.connect_attempts_rr_retries;
   params->oride.connect_attempts_timeout        = m_master.oride.connect_attempts_timeout;
   params->oride.connect_dead_policy             = m_master.oride.connect_dead_policy;
-  params->oride.post_connect_attempts_timeout   = m_master.oride.post_connect_attempts_timeout;
   params->oride.parent_connect_attempts         = m_master.oride.parent_connect_attempts;
   params->oride.parent_retry_time               = m_master.oride.parent_retry_time;
   params->oride.parent_fail_threshold           = m_master.oride.parent_fail_threshold;
   params->oride.per_parent_connect_attempts     = m_master.oride.per_parent_connect_attempts;
-  params->oride.parent_connect_timeout          = m_master.oride.parent_connect_timeout;
   params->oride.parent_failures_update_hostdb   = m_master.oride.parent_failures_update_hostdb;
   params->oride.enable_parent_timeout_markdowns = m_master.oride.enable_parent_timeout_markdowns;
   params->oride.disable_parent_markdowns        = m_master.oride.disable_parent_markdowns;
@@ -1649,7 +1634,6 @@
 
   params->send_100_continue_response = INT_TO_BOOL(m_master.send_100_continue_response);
   params->disallow_post_100_continue = INT_TO_BOOL(m_master.disallow_post_100_continue);
-  params->keepalive_internal_vc      = INT_TO_BOOL(m_master.keepalive_internal_vc);
 
   params->oride.cache_open_write_fail_action = m_master.oride.cache_open_write_fail_action;
   if (params->oride.cache_open_write_fail_action == CACHE_WL_FAIL_ACTION_READ_RETRY) {
@@ -1717,7 +1701,8 @@
   params->http_host_sni_policy = m_master.http_host_sni_policy;
   params->scheme_proto_mismatch_policy = m_master.scheme_proto_mismatch_policy;
 
-  params->oride.ssl_client_sni_policy = ats_strdup(m_master.oride.ssl_client_sni_policy);
+  params->oride.ssl_client_sni_policy     = ats_strdup(m_master.oride.ssl_client_sni_policy);
+  params->oride.ssl_client_alpn_protocols = ats_strdup(m_master.oride.ssl_client_alpn_protocols);
 
   params->negative_caching_list = m_master.negative_caching_list;
 
diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h
index 65c56d9..4ce28a1 100644
--- a/proxy/http/HttpConfig.h
+++ b/proxy/http/HttpConfig.h
@@ -676,7 +676,6 @@
   MgmtInt connect_attempts_max_retries_dead_server = 3;
   MgmtInt connect_attempts_rr_retries              = 3;
   MgmtInt connect_attempts_timeout                 = 30;
-  MgmtInt post_connect_attempts_timeout            = 1800;
 
   MgmtInt connect_dead_policy = 2;
 
@@ -687,12 +686,10 @@
   MgmtInt parent_retry_time                = 300;
   MgmtInt parent_fail_threshold            = 10;
   MgmtInt per_parent_connect_attempts      = 2;
-  MgmtInt parent_connect_timeout           = 30;
   MgmtByte enable_parent_timeout_markdowns = 0;
   MgmtByte disable_parent_markdowns        = 0;
 
   ts_seconds down_server_timeout{300};
-  MgmtInt client_abort_threshold = 1000;
 
   // open read failure retries.
   MgmtInt max_cache_open_read_retries = -1;
@@ -739,6 +736,7 @@
   char *ssl_client_cert_filename        = nullptr;
   char *ssl_client_private_key_filename = nullptr;
   char *ssl_client_ca_cert_filename     = nullptr;
+  char *ssl_client_alpn_protocols       = nullptr;
 
   // Host Resolution order
   HostResData host_res_data;
@@ -842,7 +840,6 @@
 
   MgmtByte send_100_continue_response = 0;
   MgmtByte disallow_post_100_continue = 0;
-  MgmtByte keepalive_internal_vc      = 0;
 
   MgmtByte server_session_sharing_pool = TS_SERVER_SESSION_SHARING_POOL_THREAD;
 
@@ -927,6 +924,7 @@
   ats_free(reverse_proxy_no_host_redirect);
   ats_free(redirect_actions_string);
   ats_free(oride.ssl_client_sni_policy);
+  ats_free(oride.ssl_client_alpn_protocols);
   ats_free(oride.host_res_data.conf_value);
 
   delete connect_ports;
diff --git a/proxy/http/HttpConnectionCount.cc b/proxy/http/HttpConnectionCount.cc
index 009ea8f..3d9318f 100644
--- a/proxy/http/HttpConnectionCount.cc
+++ b/proxy/http/HttpConnectionCount.cc
@@ -105,30 +105,6 @@
 }
 
 bool
-Config_Update_Conntrack_Queue_Size(const char *name, RecDataT dtype, RecData data, void *cookie)
-{
-  auto config = static_cast<OutboundConnTrack::GlobalConfig *>(cookie);
-
-  if (RECD_INT == dtype) {
-    config->queue_size = data.rec_int;
-    return true;
-  }
-  return false;
-}
-
-bool
-Config_Update_Conntrack_Queue_Delay(const char *name, RecDataT dtype, RecData data, void *cookie)
-{
-  auto config = static_cast<OutboundConnTrack::GlobalConfig *>(cookie);
-
-  if (RECD_INT == dtype && data.rec_int > 0) {
-    config->queue_delay = std::chrono::milliseconds(data.rec_int);
-    return true;
-  }
-  return false;
-}
-
-bool
 Config_Update_Conntrack_Match(const char *name, RecDataT dtype, RecData data, void *cookie)
 {
   auto config = static_cast<OutboundConnTrack::TxnConfig *>(cookie);
@@ -171,8 +147,6 @@
   Enable_Config_Var(CONFIG_VAR_MIN, &Config_Update_Conntrack_Min, txn);
   Enable_Config_Var(CONFIG_VAR_MAX, &Config_Update_Conntrack_Max, txn);
   Enable_Config_Var(CONFIG_VAR_MATCH, &Config_Update_Conntrack_Match, txn);
-  Enable_Config_Var(CONFIG_VAR_QUEUE_SIZE, &Config_Update_Conntrack_Queue_Size, global);
-  Enable_Config_Var(CONFIG_VAR_QUEUE_DELAY, &Config_Update_Conntrack_Queue_Delay, global);
   Enable_Config_Var(CONFIG_VAR_ALERT_DELAY, &Config_Update_Conntrack_Alert_Delay, global);
 }
 
@@ -269,13 +243,13 @@
   static const ts::BWFormat header_fmt{R"({{"count": {}, "list": [
 )"};
   static const ts::BWFormat item_fmt{
-    R"(  {{"type": "{}", "ip": "{}", "fqdn": "{}", "current": {}, "max": {}, "blocked": {}, "queued": {}, "alert": {}}},
+    R"(  {{"type": "{}", "ip": "{}", "fqdn": "{}", "current": {}, "max": {}, "blocked": {}, "alert": {}}},
 )"};
   static const std::string_view trailer{" \n]}"};
 
   static const auto printer = [](ts::BufferWriter &w, Group const *g) -> ts::BufferWriter & {
     w.print(item_fmt, g->_match_type, g->_addr, g->_fqdn, g->_count.load(), g->_count_max.load(), g->_blocked.load(),
-            g->_rescheduled.load(), g->get_last_alert_epoch_time());
+            g->get_last_alert_epoch_time());
     return w;
   };
 
@@ -310,18 +284,17 @@
   self_type::get(groups);
 
   if (groups.size()) {
-    fprintf(f, "\nUpstream Connection Tracking\n%7s | %5s | %10s | %24s | %33s | %8s |\n", "Current", "Block", "Queue", "Address",
-            "Hostname Hash", "Match");
-    fprintf(f, "------|-------|---------|--------------------------|-----------------------------------|----------|\n");
+    fprintf(f, "\nUpstream Connection Tracking\n%7s | %5s | %24s | %33s | %8s |\n", "Current", "Block", "Address", "Hostname Hash",
+            "Match");
+    fprintf(f, "------|-------|--------------------------|-----------------------------------|----------|\n");
 
     for (Group const *g : groups) {
       ts::LocalBufferWriter<128> w;
-      w.print("{:7} | {:5} | {:5} | {:24} | {:33} | {:8} |\n", g->_count.load(), g->_blocked.load(), g->_rescheduled.load(),
-              g->_addr, g->_hash, g->_match_type);
+      w.print("{:7} | {:5} | {:24} | {:33} | {:8} |\n", g->_count.load(), g->_blocked.load(), g->_addr, g->_hash, g->_match_type);
       fwrite(w.data(), w.size(), 1, f);
     }
 
-    fprintf(f, "------|-------|-------|--------------------------|-----------------------------------|----------|\n");
+    fprintf(f, "------|-------|--------------------------|-----------------------------------|----------|\n");
   }
 }
 
@@ -376,12 +349,11 @@
 {
   time_t lat; // last alert time (epoch seconds)
 
-  if ((_g->_blocked > 0 || _g->_rescheduled > 0) && _g->should_alert(&lat)) {
-    auto blocked     = _g->_blocked.exchange(0);
-    auto rescheduled = _g->_rescheduled.exchange(0);
+  if (_g->_blocked > 0 && _g->should_alert(&lat)) {
+    auto blocked = _g->_blocked.exchange(0);
     ts::LocalBufferWriter<256> w;
-    w.print("upstream unblocked: [{}] count={} limit={} group=({}) blocked={} queued={} upstream={}\0",
-            ts::bwf::Date(lat, "%b %d %H:%M:%S"sv), count, config->max, *_g, blocked, rescheduled, addr);
+    w.print("upstream unblocked: [{}] count={} limit={} group=({}) blocked={} upstream={}\0",
+            ts::bwf::Date(lat, "%b %d %H:%M:%S"sv), count, config->max, *_g, blocked, addr);
     Debug(DEBUG_TAG, "%s", w.data());
     Note("%s", w.data());
   }
@@ -391,14 +363,13 @@
 OutboundConnTrack::TxnState::Warn_Blocked(const TxnConfig *config, int64_t sm_id, int count, sockaddr const *addr,
                                           char const *debug_tag)
 {
-  bool alert_p     = _g->should_alert();
-  auto blocked     = alert_p ? _g->_blocked.exchange(0) : _g->_blocked.load();
-  auto rescheduled = alert_p ? _g->_rescheduled.exchange(0) : _g->_rescheduled.load();
+  bool alert_p = _g->should_alert();
+  auto blocked = alert_p ? _g->_blocked.exchange(0) : _g->_blocked.load();
 
   if (alert_p || debug_tag) {
     ts::LocalBufferWriter<256> w;
-    w.print("[{}] too many connections: count={} limit={} group=({}) blocked={} queued={} upstream={}\0", sm_id, count, config->max,
-            *_g, blocked, rescheduled, addr);
+    w.print("[{}] too many connections: count={} limit={} group=({}) blocked={} upstream={}\0", sm_id, count, config->max, *_g,
+            blocked, addr);
 
     if (debug_tag) {
       Debug(debug_tag, "%s", w.data());
diff --git a/proxy/http/HttpConnectionCount.h b/proxy/http/HttpConnectionCount.h
index 494d2ca..19a3075 100644
--- a/proxy/http/HttpConnectionCount.h
+++ b/proxy/http/HttpConnectionCount.h
@@ -77,9 +77,7 @@
 
   /** Static configuration values. */
   struct GlobalConfig {
-    int queue_size{0};                          ///< Maximum delayed transactions.
-    std::chrono::milliseconds queue_delay{100}; ///< Reschedule / queue delay in ms.
-    std::chrono::seconds alert_delay{60};       ///< Alert delay in seconds.
+    std::chrono::seconds alert_delay{60}; ///< Alert delay in seconds.
   };
 
   // The names of the configuration values.
@@ -88,8 +86,6 @@
   static constexpr std::string_view CONFIG_VAR_MAX{"proxy.config.http.per_server.connection.max"_sv};
   static constexpr std::string_view CONFIG_VAR_MIN{"proxy.config.http.per_server.connection.min"_sv};
   static constexpr std::string_view CONFIG_VAR_MATCH{"proxy.config.http.per_server.connection.match"_sv};
-  static constexpr std::string_view CONFIG_VAR_QUEUE_SIZE{"proxy.config.http.per_server.connection.queue_size"_sv};
-  static constexpr std::string_view CONFIG_VAR_QUEUE_DELAY{"proxy.config.http.per_server.connection.queue_delay"_sv};
   static constexpr std::string_view CONFIG_VAR_ALERT_DELAY{"proxy.config.http.per_server.connection.alert_delay"_sv};
 
   /// A record for the outbound connection count.
@@ -122,7 +118,6 @@
     std::atomic<int> _count{0};         ///< Number of outbound connections.
     std::atomic<int> _count_max{0};     ///< largest observed @a count value.
     std::atomic<int> _blocked{0};       ///< Number of outbound connections blocked since last alert.
-    std::atomic<int> _rescheduled{0};   ///< # of connection reschedules.
     std::atomic<int> _in_queue{0};      ///< # of connections queued, waiting for a connection.
     std::atomic<Ticker> _last_alert{0}; ///< Absolute time of the last alert.
 
@@ -168,8 +163,6 @@
     void dequeue();
     /// Note blocking a transaction.
     void blocked();
-    /// Note a rescheduling
-    void rescheduled();
     /// Clear all reservations.
     void clear();
     /// Drop the reservation - assume it will be cleaned up elsewhere.
@@ -392,12 +385,6 @@
   ++_g->_blocked;
 }
 
-inline void
-OutboundConnTrack::TxnState::rescheduled()
-{
-  ++_g->_rescheduled;
-}
-
 /* === Linkage === */
 inline auto
 OutboundConnTrack::Linkage::next_ptr(value_type *value) -> value_type *&
diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc
index 2eb03f8..c1c5db0 100644
--- a/proxy/http/HttpDebugNames.cc
+++ b/proxy/http/HttpDebugNames.cc
@@ -338,8 +338,8 @@
     return "TS_EVENT_VCONN_CLOSE";
   case TS_EVENT_LIFECYCLE_MSG:
     return "TS_EVENT_LIFECYCLE_MSG";
-  case TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE:
-    return "TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE";
+  case TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE:
+    return "TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE";
   case TS_EVENT_MGMT_UPDATE:
     return "TS_EVENT_MGMT_UPDATE";
   case TS_EVENT_INTERNAL_60200:
@@ -586,6 +586,8 @@
     return "TS_HTTP_POST_REMAP_HOOK";
   case TS_HTTP_RESPONSE_CLIENT_HOOK:
     return "TS_HTTP_RESPONSE_CLIENT_HOOK";
+  case TS_HTTP_REQUEST_CLIENT_HOOK:
+    return "TS_HTTP_REQUEST_CLIENT_HOOK";
   case TS_HTTP_LAST_HOOK:
     return "TS_HTTP_LAST_HOOK";
   case TS_VCONN_START_HOOK:
diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc
index 68f6810..6b5a1fa 100644
--- a/proxy/http/HttpProxyServerMain.cc
+++ b/proxy/http/HttpProxyServerMain.cc
@@ -50,6 +50,8 @@
 
 HttpSessionAccept *plugin_http_accept             = nullptr;
 HttpSessionAccept *plugin_http_transparent_accept = nullptr;
+extern std::function<PoolableSession *()> create_h1_server_session;
+extern std::map<int, std::function<ProxySession *()>> ProtocolSessionCreateMap;
 
 static SLL<SSLNextProtocolAccept> ssl_plugin_acceptors;
 static Ptr<ProxyMutex> ssl_plugin_mutex;
@@ -221,6 +223,8 @@
   if (port.m_session_protocol_preference.intersects(HTTP2_PROTOCOL_SET)) {
     probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_HTTP2, new Http2SessionAccept(accept_opt));
   }
+  ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_1_0, create_h1_server_session});
+  ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_1_1, create_h1_server_session});
 
   if (port.isSSL()) {
     SSLNextProtocolAccept *ssl = new SSLNextProtocolAccept(probe, port.m_transparent_passthrough);
@@ -247,11 +251,11 @@
 
     quic->enableProtocols(port.m_session_protocol_preference);
 
-    // HTTP/0.9 over QUIC draft-27 (for interop only, will be removed)
-    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D27, new Http3SessionAccept(accept_opt));
+    // HTTP/0.9 over QUIC draft-29 (for interop only, will be removed)
+    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D29, new Http3SessionAccept(accept_opt));
 
-    // HTTP/3 draft-27
-    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D27, new Http3SessionAccept(accept_opt));
+    // HTTP/3 draft-29
+    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D29, new Http3SessionAccept(accept_opt));
 
     // HTTP/0.9 over QUIC (for interop only, will be removed)
     quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC, new Http3SessionAccept(accept_opt));
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index 97519be..e53889f 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -325,15 +325,7 @@
   if (t_state.api_txn_connect_timeout_value != -1) {
     retval = HRTIME_MSECONDS(t_state.api_txn_connect_timeout_value);
   } else {
-    int connect_timeout;
-    if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) {
-      connect_timeout = t_state.txn_conf->post_connect_attempts_timeout;
-    } else if (t_state.current.server == &t_state.parent_info) {
-      connect_timeout = t_state.txn_conf->parent_connect_timeout;
-    } else {
-      connect_timeout = t_state.txn_conf->connect_attempts_timeout;
-    }
-    retval = HRTIME_SECONDS(connect_timeout);
+    retval = HRTIME_SECONDS(t_state.txn_conf->connect_attempts_timeout);
   }
   return retval;
 }
@@ -1674,7 +1666,7 @@
 // void HttpSM::handle_api_return()
 //
 //   Figures out what to do after calling api callouts
-//    have finished.  This messy and I would like
+//    have finished.  This is messy and I would like
 //    to come up with a cleaner way to handle the api
 //    return.  The way we are doing things also makes a
 //    mess of set_next_state()
@@ -1827,9 +1819,20 @@
 PoolableSession *
 HttpSM::create_server_session(NetVConnection *netvc)
 {
-  HttpTransact::State &s  = this->t_state;
-  PoolableSession *retval = httpServerSessionAllocator.alloc();
+  // Figure out what protocol was negotiated
+  int proto_index      = SessionProtocolNameRegistry::INVALID;
+  auto const *sslnetvc = dynamic_cast<ALPNSupport *>(netvc);
+  if (sslnetvc) {
+    proto_index = sslnetvc->get_negotiated_protocol_id();
+  }
+  // No ALPN occurred. Assume it was HTTP/1.x and hope for the best
+  if (proto_index == SessionProtocolNameRegistry::INVALID) {
+    proto_index = TS_ALPN_PROTOCOL_INDEX_HTTP_1_1;
+  }
 
+  PoolableSession *retval = ProxySession::create_outbound_session(proto_index);
+
+  HttpTransact::State &s       = this->t_state;
   retval->sharing_pool         = static_cast<TSServerSessionSharingPoolType>(s.http_config_param->server_session_sharing_pool);
   retval->sharing_match        = static_cast<TSServerSessionSharingMatchMask>(s.txn_conf->server_session_sharing_match);
   MIOBuffer *netvc_read_buffer = new_MIOBuffer(HTTP_SERVER_RESP_HDR_BUFFER_INDEX);
@@ -3679,7 +3682,14 @@
 {
   STATE_ENTER(&HttpSM::tunnel_handler_post_server, event);
 
-  server_request_body_bytes = c->bytes_written;
+  // If is_using_post_buffer has been used, then this handler gets called
+  // twice, once with the buffered request body bytes and a second time with
+  // the (now) zero length user agent buffer. See wait_for_full_body where
+  // these bytes are read. Don't clobber the server_request_body_bytes with
+  // zero on that second read.
+  if (server_request_body_bytes == 0) {
+    server_request_body_bytes = c->bytes_written;
+  }
 
   switch (event) {
   case VC_EVENT_EOS:
@@ -5195,33 +5205,11 @@
 
       ink_assert(pending_action.empty()); // in case of reschedule must not have already pending.
 
-      // If the queue is disabled, reschedule.
-      if (t_state.http_config_param->global_outbound_conntrack.queue_size < 0) {
-        ct_state.enqueue();
-        ct_state.rescheduled();
-        pending_action = eventProcessor.schedule_in(
-          this, HRTIME_MSECONDS(t_state.http_config_param->global_outbound_conntrack.queue_delay.count()));
-      } else if (t_state.http_config_param->global_outbound_conntrack.queue_size > 0) { // queue enabled, check for a slot
-        auto wcount = ct_state.enqueue();
-        if (wcount < t_state.http_config_param->global_outbound_conntrack.queue_size) {
-          ct_state.rescheduled();
-          SMDebug("http", "%s", lbw().print("queued for {}\0", t_state.current.server->dst_addr).data());
-          pending_action = eventProcessor.schedule_in(
-            this, HRTIME_MSECONDS(t_state.http_config_param->global_outbound_conntrack.queue_delay.count()));
-        } else {              // the queue is full
-          ct_state.dequeue(); // release the queue slot
-          ct_state.blocked(); // note the blockage.
-          HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat);
-          send_origin_throttled_response();
-        }
-      } else { // queue size is 0, always block.
-        ct_state.blocked();
-        HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat);
-        ct_state.Warn_Blocked(&t_state.txn_conf->outbound_conntrack, sm_id, ccount - 1, &t_state.current.server->dst_addr.sa,
-                              debug_on && is_debug_tag_set("http") ? "http" : nullptr);
-        send_origin_throttled_response();
-      }
-
+      ct_state.blocked();
+      HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat);
+      ct_state.Warn_Blocked(&t_state.txn_conf->outbound_conntrack, sm_id, ccount - 1, &t_state.current.server->dst_addr.sa,
+                            debug_on && is_debug_tag_set("http") ? "http" : nullptr);
+      send_origin_throttled_response();
       return;
     } else {
       ct_state.Note_Unblocked(&t_state.txn_conf->outbound_conntrack, ccount, &t_state.current.server->dst_addr.sa);
@@ -5342,6 +5330,16 @@
   opt.set_ssl_client_cert_name(t_state.txn_conf->ssl_client_cert_filename);
   opt.ssl_client_private_key_name = t_state.txn_conf->ssl_client_private_key_filename;
   opt.ssl_client_ca_cert_name     = t_state.txn_conf->ssl_client_ca_cert_filename;
+  if (is_private()) {
+    // If the connection to origin is private, don't try to negotiate higher overhead protocols.
+    opt.alpn_protocols_array_size = -1;
+    SMDebug("ssl_alpn", "Clear ALPN for private session");
+  } else if (t_state.txn_conf->ssl_client_alpn_protocols != nullptr) {
+    opt.alpn_protocols_array_size = MAX_ALPN_STRING;
+    SMDebug("ssl_alpn", "Setting ALPN to: %s", t_state.txn_conf->ssl_client_alpn_protocols);
+    convert_alpn_to_wire_format(t_state.txn_conf->ssl_client_alpn_protocols, opt.alpn_protocols_array,
+                                opt.alpn_protocols_array_size);
+  }
 
   if (tls_upstream) {
     SMDebug("http", "calling sslNetProcessor.connect_re");
@@ -5960,10 +5958,17 @@
 
     // Next order of business if copy the remaining data from the
     //  header buffer into new buffer
-    client_request_body_bytes =
+    int64_t num_body_bytes =
       post_buffer->write(ua_txn->get_remote_reader(), chunked ? ua_txn->get_remote_reader()->read_avail() : post_bytes);
 
-    ua_txn->get_remote_reader()->consume(client_request_body_bytes);
+    // If is_using_post_buffer has been used, then client_request_body_bytes
+    // will have already been set in wait_for_full_body and there will be
+    // zero bytes in this user agent buffer. We don't want to clobber
+    // client_request_body_bytes with a zero value here in those cases.
+    if (client_request_body_bytes == 0) {
+      client_request_body_bytes = num_body_bytes;
+    }
+    ua_txn->get_remote_reader()->consume(num_body_bytes);
     p = tunnel.add_producer(ua_entry->vc, post_bytes - transfered_bytes, buf_start, &HttpSM::tunnel_handler_post_ua, HT_HTTP_CLIENT,
                             "user agent post");
   }
@@ -5998,6 +6003,8 @@
     break;
   }
 
+  this->setup_client_request_plugin_agents(p);
+
   // The user agent may support chunked (HTTP/1.1) or not (HTTP/2)
   // In either case, the server will support chunked (HTTP/1.1)
   if (chunked) {
@@ -6765,7 +6772,7 @@
   transform_info.entry->in_tunnel = true;
   ua_entry->in_tunnel             = true;
 
-  this->setup_plugin_agents(p, client_response_hdr_bytes);
+  this->setup_client_response_plugin_agents(p, client_response_hdr_bytes);
 
   if (t_state.client_info.receive_chunked_response) {
     tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_CHUNK_CONTENT);
@@ -6888,7 +6895,7 @@
   ua_entry->in_tunnel     = true;
   server_entry->in_tunnel = true;
 
-  this->setup_plugin_agents(p, client_response_hdr_bytes);
+  this->setup_client_response_plugin_agents(p, client_response_hdr_bytes);
 
   // If the incoming server response is chunked and the client does not
   // expect a chunked response, then dechunk it.  Otherwise, if the
@@ -7036,13 +7043,29 @@
 }
 
 void
-HttpSM::setup_plugin_agents(HttpTunnelProducer *p, int num_header_bytes)
+HttpSM::setup_client_response_plugin_agents(HttpTunnelProducer *p, int num_header_bytes)
 {
-  APIHook *agent           = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK);
-  has_active_plugin_agents = agent != nullptr;
+  APIHook *agent                    = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK);
+  has_active_response_plugin_agents = agent != nullptr;
   while (agent) {
     INKVConnInternal *contp = static_cast<INKVConnInternal *>(agent->m_cont);
-    tunnel.add_consumer(contp, p->vc, &HttpSM::tunnel_handler_plugin_agent, HT_HTTP_CLIENT, "plugin agent", num_header_bytes);
+    tunnel.add_consumer(contp, p->vc, &HttpSM::tunnel_handler_plugin_agent, HT_HTTP_CLIENT, "response plugin agent",
+                        num_header_bytes);
+    // We don't put these in the SM VC table because the tunnel
+    // will clean them up in do_io_close().
+    agent = agent->next();
+  }
+}
+
+void
+HttpSM::setup_client_request_plugin_agents(HttpTunnelProducer *p, int num_header_bytes)
+{
+  APIHook *agent                   = txn_hook_get(TS_HTTP_REQUEST_CLIENT_HOOK);
+  has_active_request_plugin_agents = agent != nullptr;
+  while (agent) {
+    INKVConnInternal *contp = static_cast<INKVConnInternal *>(agent->m_cont);
+    tunnel.add_consumer(contp, p->vc, &HttpSM::tunnel_handler_plugin_agent, HT_HTTP_CLIENT, "request plugin agent",
+                        num_header_bytes);
     // We don't put these in the SM VC table because the tunnel
     // will clean them up in do_io_close().
     agent = agent->next();
@@ -7068,12 +7091,26 @@
   // If this is set then all of the plugin agent VCs were put in
   // the VC table and cleaned up there. This handles the case where
   // something went wrong early.
-  if (!has_active_plugin_agents) {
-    APIHook *agent = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK);
-    while (agent) {
-      INKVConnInternal *contp = static_cast<INKVConnInternal *>(agent->m_cont);
-      contp->do_io_close();
-      agent = agent->next();
+  if (!has_active_response_plugin_agents) {
+    std::vector<APIHook *> agent_lists;
+    agent_lists.push_back(txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK));
+    for (auto &agent : agent_lists) {
+      while (agent) {
+        INKVConnInternal *contp = static_cast<INKVConnInternal *>(agent->m_cont);
+        contp->do_io_close();
+        agent = agent->next();
+      }
+    }
+  }
+  if (!has_active_request_plugin_agents) {
+    std::vector<APIHook *> agent_lists;
+    agent_lists.push_back(txn_hook_get(TS_HTTP_REQUEST_CLIENT_HOOK));
+    for (auto &agent : agent_lists) {
+      while (agent) {
+        INKVConnInternal *contp = static_cast<INKVConnInternal *>(agent->m_cont);
+        contp->do_io_close();
+        agent = agent->next();
+      }
     }
   }
 }
diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h
index e7d1efe..3a41ad8 100644
--- a/proxy/http/HttpSM.h
+++ b/proxy/http/HttpSM.h
@@ -376,9 +376,17 @@
 
   HttpTransformInfo transform_info;
   HttpTransformInfo post_transform_info;
-  /// Set if plugin client / user agents are active.
-  /// Need primarily for cleanup.
-  bool has_active_plugin_agents = false;
+  /** A flag to keep track of whether there are active request plugin agents.
+   *
+   * This is used to guide plugin agent cleanup.
+   */
+  bool has_active_request_plugin_agents = false;
+
+  /** A flag to keep track of whether there are active response plugin agents.
+   *
+   * This is used to guide plugin agent cleanup.
+   */
+  bool has_active_response_plugin_agents = false;
 
   HttpCacheSM cache_sm;
   HttpCacheSM transform_cache_sm;
@@ -504,14 +512,23 @@
   HttpTunnelProducer *setup_cache_transfer_to_transform();
   HttpTunnelProducer *setup_transfer_from_transform_to_cache_only();
 
-  /** Configure consumers for transform plugins.
+  /** Configure consumers for client response transform plugins.
    *
    * @param[in] p The Tunnel's producer for whom transform plugins' consumers
    *   will be configured.
    * @param[in] num_header_bytes The number of header bytes in the stream.
    *   These will be skipped and not passed to the consumers of the data sink.
    */
-  void setup_plugin_agents(HttpTunnelProducer *p, int num_header_bytes);
+  void setup_client_response_plugin_agents(HttpTunnelProducer *p, int num_header_bytes = 0);
+
+  /** Configure consumers for client request transform plugins.
+   *
+   * @param[in] p The Tunnel's producer for whom transform plugins' consumers
+   *   will be configured.
+   * @param[in] num_header_bytes The number of header bytes in the stream.
+   *   These will be skipped and not passed to the consumers of the data sink.
+   */
+  void setup_client_request_plugin_agents(HttpTunnelProducer *p, int num_header_bytes = 0);
 
   HttpTransact::StateMachineAction_t last_action     = HttpTransact::SM_ACTION_UNDEFINED;
   int (HttpSM::*m_last_state)(int event, void *data) = nullptr;
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index 0944b0e..c79f698 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -5686,15 +5686,8 @@
   }
 
   // If this is an internal request, never keep alive
-  if (!s->txn_conf->keep_alive_enabled_in) {
+  if (!s->txn_conf->keep_alive_enabled_in || (vc && vc->get_is_internal_request())) {
     s->client_info.keep_alive = HTTP_NO_KEEPALIVE;
-  } else if (vc && vc->get_is_internal_request()) {
-    // Following the trail of JIRAs back from TS-4960, there can be issues with
-    // EOS event delivery when using keepalive on internal PluginVC session. As
-    // an interim measure, if proxy.config.http.keepalive_internal_vc is set,
-    // we will obey the incoming transaction's keepalive request.
-    s->client_info.keep_alive =
-      s->http_config_param->keepalive_internal_vc ? incoming_request->keep_alive_get() : HTTP_NO_KEEPALIVE;
   } else {
     s->client_info.keep_alive = incoming_request->keep_alive_get();
   }
diff --git a/proxy/http/Makefile.am b/proxy/http/Makefile.am
index d93caba..dab3678 100644
--- a/proxy/http/Makefile.am
+++ b/proxy/http/Makefile.am
@@ -33,6 +33,7 @@
 	-I$(abs_top_srcdir)/proxy/logging \
 	-I$(abs_top_srcdir)/proxy/http2 \
 	-I$(abs_top_srcdir)/proxy/http3 \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES) \
 	@YAMLCPP_INCLUDES@
 
@@ -110,7 +111,7 @@
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
 	$(top_builddir)/mgmt/libmgmt_p.la \
 	$(top_builddir)/iocore/utils/libinkutils.a \
-	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ @HWLOC_LIBS@ \
 	@LIBCAP@
 
 test_PreWarm_CPPFLAGS = \
diff --git a/proxy/http/remap/Makefile.am b/proxy/http/remap/Makefile.am
index 9d23431..5b09475 100644
--- a/proxy/http/remap/Makefile.am
+++ b/proxy/http/remap/Makefile.am
@@ -29,6 +29,7 @@
 	-I$(abs_top_srcdir)/proxy/hdrs \
 	-I$(abs_top_srcdir)/proxy/shared \
 	-I$(abs_top_srcdir)/proxy/http \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES) \
 	@YAMLCPP_INCLUDES@
 
@@ -75,7 +76,7 @@
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/mgmt/libmgmt_p.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@
+	@SWOC_LIBS@ @HWLOC_LIBS@
 
 clang-tidy-local: $(libhttp_remap_a_SOURCES)
 	$(CXX_Clang_Tidy)
@@ -142,6 +143,7 @@
   $(top_builddir)/mgmt/libmgmt_p.la \
   $(top_builddir)/iocore/utils/libinkutils.a \
   $(top_builddir)/src/tscpp/util/libtscpputil.la \
+	@SWOC_LIBS@ \
 	@YAMLCPP_LIBS@ \
 	@HWLOC_LIBS@
 
@@ -173,6 +175,7 @@
   $(top_builddir)/mgmt/libmgmt_p.la \
   $(top_builddir)/iocore/utils/libinkutils.a \
   $(top_builddir)/src/tscpp/util/libtscpputil.la \
+	@SWOC_LIBS@ \
 	@YAMLCPP_LIBS@ \
 	@HWLOC_LIBS@
 
@@ -204,6 +207,7 @@
   $(top_builddir)/mgmt/libmgmt_p.la \
   $(top_builddir)/iocore/utils/libinkutils.a \
   $(top_builddir)/src/tscpp/util/libtscpputil.la \
+	@SWOC_LIBS@ \
 	@YAMLCPP_LIBS@ \
 	@HWLOC_LIBS@
 
diff --git a/proxy/http/remap/NextHopSelectionStrategy.h b/proxy/http/remap/NextHopSelectionStrategy.h
index 54c0df5..e63a3a4 100644
--- a/proxy/http/remap/NextHopSelectionStrategy.h
+++ b/proxy/http/remap/NextHopSelectionStrategy.h
@@ -22,6 +22,7 @@
  */
 
 #pragma once
+#include <mutex>
 
 #include <utility>
 
diff --git a/proxy/http/remap/PluginDso.h b/proxy/http/remap/PluginDso.h
index c139081..e8d359d 100644
--- a/proxy/http/remap/PluginDso.h
+++ b/proxy/http/remap/PluginDso.h
@@ -73,6 +73,11 @@
   const fs::path &runtimePath() const;
   time_t modTime() const;
   void *dlOpenHandle() const;
+  void *
+  dlh() const
+  {
+    return _dlh;
+  }
 
   /* List used by the plugin factory */
   using self_type  = PluginDso; ///< Self reference type.
diff --git a/proxy/http/remap/RemapConfig.cc b/proxy/http/remap/RemapConfig.cc
index a61710e..9df1c0f 100644
--- a/proxy/http/remap/RemapConfig.cc
+++ b/proxy/http/remap/RemapConfig.cc
@@ -914,7 +914,6 @@
   const char *errStr;
 
   Tokenizer whiteTok(" \t");
-  bool alarm_already = false;
 
   // Vars to parse line in file
   char *tok_state, *cur_line, *cur_line_tmp;
@@ -1153,7 +1152,7 @@
             if (refinfo_error) {
               snprintf(errStrBuf, sizeof(errStrBuf), "%s Incorrect Referer regular expression \"%s\" at line %d - %s", modulePrefix,
                        bti->paramv[j - 1], cln + 1, refinfo_error_buf);
-              SignalError(errStrBuf, alarm_already);
+              Error("%s", errStrBuf);
               delete ri;
               ri = nullptr;
             }
@@ -1335,7 +1334,7 @@
   MAP_ERROR:
 
     snprintf(errBuf, sizeof(errBuf), "%s failed to add remap rule at %s line %d: %s", modulePrefix, path, cln + 1, errStr);
-    SignalError(errBuf, alarm_already);
+    Error("%s", errBuf);
 
     delete reg_map;
     delete new_mapping;
diff --git a/proxy/http/remap/RemapPluginInfo.cc b/proxy/http/remap/RemapPluginInfo.cc
index 736fd4d..9e19ab9 100644
--- a/proxy/http/remap/RemapPluginInfo.cc
+++ b/proxy/http/remap/RemapPluginInfo.cc
@@ -134,6 +134,7 @@
   ink_zero(ri);
   ri.size            = sizeof(ri);
   ri.tsremap_version = TSREMAP_VERSION;
+  ri.plugin_info     = reinterpret_cast<TSRemapPluginInfo>(this);
 
   setPluginContext();
 
diff --git a/proxy/http/remap/UrlRewrite.cc b/proxy/http/remap/UrlRewrite.cc
index e2ec415..2fede9c 100644
--- a/proxy/http/remap/UrlRewrite.cc
+++ b/proxy/http/remap/UrlRewrite.cc
@@ -58,7 +58,6 @@
 
   config_file_path = RecConfigReadConfigPath("proxy.config.url_remap.filename", ts::filename::REMAP);
   if (!config_file_path) {
-    pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, "Unable to find proxy.config.url_remap.filename");
     Warning("%s Unable to locate %s. No remappings in effect", modulePrefix, ts::filename::REMAP);
     return false;
   }
@@ -66,7 +65,6 @@
   this->ts_name = nullptr;
   REC_ReadConfigStringAlloc(this->ts_name, "proxy.config.proxy_name");
   if (this->ts_name == nullptr) {
-    pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, "Unable to read proxy.config.proxy_name");
     Warning("%s Unable to determine proxy name.  Incorrect redirects could be generated", modulePrefix);
     this->ts_name = ats_strdup("");
   }
@@ -74,7 +72,6 @@
   this->http_default_redirect_url = nullptr;
   REC_ReadConfigStringAlloc(this->http_default_redirect_url, "proxy.config.http.referer_default_redirect");
   if (this->http_default_redirect_url == nullptr) {
-    pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, "Unable to read proxy.config.http.referer_default_redirect");
     Warning("%s Unable to determine default redirect url for \"referer\" filter.", modulePrefix);
     this->http_default_redirect_url = ats_strdup("http://www.apache.org");
   }
diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index ce9d8c9..65622a8 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -465,9 +465,11 @@
 http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_t buf_len, uint32_t *len_read, HpackHandle &handle,
                            bool &trailing_header, uint32_t maximum_table_size)
 {
-  const MIMEField *field;
-  const char *value;
-  int len;
+  const MIMEField *field  = nullptr;
+  const char *name        = nullptr;
+  int name_len            = 0;
+  const char *value       = nullptr;
+  int value_len           = 0;
   bool is_trailing_header = trailing_header;
   int64_t result = hpack_decode_header_block(handle, hdr, buf_start, buf_len, Http2::max_header_list_size, maximum_table_size);
 
@@ -492,14 +494,14 @@
     expected_pseudo_header_count = 0;
   }
   for (auto &field : *hdr) {
-    value = field.name_get(&len);
+    name = field.name_get(&name_len);
     // Pseudo headers must appear before regular headers
-    if (len && value[0] == ':') {
+    if (name_len && name[0] == ':') {
       ++pseudo_header_count;
       if (pseudo_header_count > expected_pseudo_header_count) {
         return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
       }
-    } else if (len <= 0) {
+    } else if (name_len <= 0) {
       return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
     } else {
       if (pseudo_header_count != expected_pseudo_header_count) {
@@ -521,8 +523,8 @@
   // :path pseudo header MUST NOT empty for http or https URIs
   field = hdr->field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size());
   if (field) {
-    field->value_get(&len);
-    if (len == 0) {
+    field->value_get(&value_len);
+    if (value_len == 0) {
       return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
     }
   }
@@ -538,8 +540,8 @@
   // value other than "trailers".
   field = hdr->field_find(MIME_FIELD_TE, MIME_LEN_TE);
   if (field) {
-    value = field->value_get(&len);
-    if (!(len == 8 && memcmp(value, "trailers", 8) == 0)) {
+    value = field->value_get(&value_len);
+    if (!(value_len == 8 && memcmp(value, "trailers", 8) == 0)) {
       return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
     }
   }
@@ -565,35 +567,36 @@
 }
 
 // Initialize this subsystem with librecords configs (for now)
-uint32_t Http2::max_concurrent_streams_in       = 100;
-uint32_t Http2::min_concurrent_streams_in       = 10;
-uint32_t Http2::max_active_streams_in           = 0;
-bool Http2::throttling                          = false;
-uint32_t Http2::stream_priority_enabled         = 0;
-uint32_t Http2::initial_window_size             = 65535;
-uint32_t Http2::max_frame_size                  = 16384;
-uint32_t Http2::header_table_size               = 4096;
-uint32_t Http2::max_header_list_size            = 4294967295;
-uint32_t Http2::accept_no_activity_timeout      = 120;
-uint32_t Http2::no_activity_timeout_in          = 120;
-uint32_t Http2::active_timeout_in               = 0;
-uint32_t Http2::push_diary_size                 = 256;
-uint32_t Http2::zombie_timeout_in               = 0;
-float Http2::stream_error_rate_threshold        = 0.1;
-uint32_t Http2::stream_error_sampling_threshold = 10;
-uint32_t Http2::max_settings_per_frame          = 7;
-uint32_t Http2::max_settings_per_minute         = 14;
-uint32_t Http2::max_settings_frames_per_minute  = 14;
-uint32_t Http2::max_ping_frames_per_minute      = 60;
-uint32_t Http2::max_priority_frames_per_minute  = 120;
-float Http2::min_avg_window_update              = 2560.0;
-uint32_t Http2::con_slow_log_threshold          = 0;
-uint32_t Http2::stream_slow_log_threshold       = 0;
-uint32_t Http2::header_table_size_limit         = 65536;
-uint32_t Http2::write_buffer_block_size         = 262144;
-float Http2::write_size_threshold               = 0.5;
-uint32_t Http2::write_time_threshold            = 100;
-uint32_t Http2::buffer_water_mark               = 0;
+uint32_t Http2::max_concurrent_streams_in            = 100;
+uint32_t Http2::min_concurrent_streams_in            = 10;
+uint32_t Http2::max_active_streams_in                = 0;
+bool Http2::throttling                               = false;
+uint32_t Http2::stream_priority_enabled              = 0;
+uint32_t Http2::initial_window_size_in               = 65535;
+Http2FlowControlPolicy Http2::flow_control_policy_in = Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM;
+uint32_t Http2::max_frame_size                       = 16384;
+uint32_t Http2::header_table_size                    = 4096;
+uint32_t Http2::max_header_list_size                 = 4294967295;
+uint32_t Http2::accept_no_activity_timeout           = 120;
+uint32_t Http2::no_activity_timeout_in               = 120;
+uint32_t Http2::active_timeout_in                    = 0;
+uint32_t Http2::push_diary_size                      = 256;
+uint32_t Http2::zombie_timeout_in                    = 0;
+float Http2::stream_error_rate_threshold             = 0.1;
+uint32_t Http2::stream_error_sampling_threshold      = 10;
+uint32_t Http2::max_settings_per_frame               = 7;
+uint32_t Http2::max_settings_per_minute              = 14;
+uint32_t Http2::max_settings_frames_per_minute       = 14;
+uint32_t Http2::max_ping_frames_per_minute           = 60;
+uint32_t Http2::max_priority_frames_per_minute       = 120;
+float Http2::min_avg_window_update                   = 2560.0;
+uint32_t Http2::con_slow_log_threshold               = 0;
+uint32_t Http2::stream_slow_log_threshold            = 0;
+uint32_t Http2::header_table_size_limit              = 65536;
+uint32_t Http2::write_buffer_block_size              = 262144;
+float Http2::write_size_threshold                    = 0.5;
+uint32_t Http2::write_time_threshold                 = 100;
+uint32_t Http2::buffer_water_mark                    = 0;
 
 void
 Http2::init()
@@ -602,7 +605,16 @@
   REC_EstablishStaticConfigInt32U(min_concurrent_streams_in, "proxy.config.http2.min_concurrent_streams_in");
   REC_EstablishStaticConfigInt32U(max_active_streams_in, "proxy.config.http2.max_active_streams_in");
   REC_EstablishStaticConfigInt32U(stream_priority_enabled, "proxy.config.http2.stream_priority_enabled");
-  REC_EstablishStaticConfigInt32U(initial_window_size, "proxy.config.http2.initial_window_size_in");
+  REC_EstablishStaticConfigInt32U(initial_window_size_in, "proxy.config.http2.initial_window_size_in");
+
+  uint32_t flow_control_policy_in_int = 0;
+  REC_EstablishStaticConfigInt32U(flow_control_policy_in_int, "proxy.config.http2.flow_control.policy_in");
+  if (flow_control_policy_in_int > 2) {
+    Error("Invalid value for proxy.config.http2.flow_control.policy_in: %d", flow_control_policy_in_int);
+    flow_control_policy_in_int = 0;
+  }
+  flow_control_policy_in = static_cast<Http2FlowControlPolicy>(flow_control_policy_in_int);
+
   REC_EstablishStaticConfigInt32U(max_frame_size, "proxy.config.http2.max_frame_size");
   REC_EstablishStaticConfigInt32U(header_table_size, "proxy.config.http2.header_table_size");
   REC_EstablishStaticConfigInt32U(max_header_list_size, "proxy.config.http2.max_header_list_size");
@@ -630,7 +642,7 @@
   // If any settings is broken, ATS should not start
   ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, max_concurrent_streams_in}));
   ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, min_concurrent_streams_in}));
-  ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, initial_window_size}));
+  ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, initial_window_size_in}));
   ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_FRAME_SIZE, max_frame_size}));
   ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_HEADER_TABLE_SIZE, header_table_size}));
   ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, max_header_list_size}));
diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h
index 4418d68..d55e380 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -31,14 +31,15 @@
 
 class HTTPHdr;
 
-typedef unsigned Http2StreamId;
+// [RFC 9113] 5.1.1 Stream identifiers.
+using Http2StreamId = uint32_t;
 
-constexpr Http2StreamId HTTP2_CONNECTION_CONTROL_STRTEAM = 0;
-constexpr uint8_t HTTP2_FRAME_NO_FLAG                    = 0;
+constexpr Http2StreamId HTTP2_CONNECTION_CONTROL_STREAM = 0;
+constexpr uint8_t HTTP2_FRAME_NO_FLAG                   = 0;
 
 // [RFC 7540] 6.9.2. Initial Flow Control Window Size
 // the flow control window can be come negative so we need to track it with a signed type.
-typedef int32_t Http2WindowSize;
+using Http2WindowSize = int32_t;
 
 extern const char *const HTTP2_CONNECTION_PREFACE;
 const size_t HTTP2_CONNECTION_PREFACE_LEN = 24;
@@ -360,6 +361,15 @@
 ParseResult http2_convert_header_from_1_1_to_2(HTTPHdr *);
 void http2_init();
 
+/** Each of these values correspond to the flow control policy described in or
+ * records.config documentation for proxy.config.http2.flow_control.policy_in.
+ */
+enum class Http2FlowControlPolicy {
+  STATIC_SESSION_AND_STATIC_STREAM,
+  LARGE_SESSION_AND_STATIC_STREAM,
+  LARGE_SESSION_AND_DYNAMIC_STREAM,
+};
+
 // Not sure where else to put this, but figure this is as good of a start as
 // anything else.
 // Right now, only the static init() is available, which sets up some basic
@@ -373,7 +383,8 @@
   static uint32_t max_active_streams_in;
   static bool throttling;
   static uint32_t stream_priority_enabled;
-  static uint32_t initial_window_size;
+  static uint32_t initial_window_size_in;
+  static Http2FlowControlPolicy flow_control_policy_in;
   static uint32_t max_frame_size;
   static uint32_t header_table_size;
   static uint32_t max_header_list_size;
diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc
index 62c6f9f..897e7a5 100644
--- a/proxy/http2/Http2ClientSession.cc
+++ b/proxy/http2/Http2ClientSession.cc
@@ -113,7 +113,7 @@
   this->_vc->set_tcp_congestion_control(CLIENT_SIDE);
 
   this->read_buffer             = iobuf ? iobuf : new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX);
-  this->read_buffer->water_mark = connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE);
+  this->read_buffer->water_mark = connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE);
   this->_read_buffer_reader     = reader ? reader : this->read_buffer->alloc_reader();
 
   // This block size is the buffer size that we pass to SSLWriteBuffer
diff --git a/proxy/http2/Http2CommonSession.cc b/proxy/http2/Http2CommonSession.cc
index fb1cb20..b151441 100644
--- a/proxy/http2/Http2CommonSession.cc
+++ b/proxy/http2/Http2CommonSession.cc
@@ -268,13 +268,13 @@
 
   this->_read_buffer_reader->consume(nbytes);
 
-  if (!http2_frame_header_is_valid(this->current_hdr, this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE))) {
+  if (!http2_frame_header_is_valid(this->current_hdr, this->connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE))) {
     ret_error = Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
     return -1;
   }
 
   // If we know up front that the payload is too long, nuke this connection.
-  if (this->current_hdr.length > this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) {
+  if (this->current_hdr.length > this->connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) {
     ret_error = Http2ErrorCode::HTTP2_ERROR_FRAME_SIZE_ERROR;
     return -1;
   }
@@ -356,10 +356,10 @@
     Http2ErrorCode err = Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
     if (this->connection_state.get_stream_error_rate() > std::min(1.0, Http2::stream_error_rate_threshold * 2.0)) {
       ip_port_text_buffer ipb;
-      const char *client_ip = ats_ip_ntop(this->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb));
-      SiteThrottledWarning("HTTP/2 session error client_ip=%s session_id=%" PRId64
+      const char *peer_ip = ats_ip_ntop(this->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb));
+      SiteThrottledWarning("HTTP/2 session error peer_ip=%s session_id=%" PRId64
                            " closing a connection, because its stream error rate (%f) exceeded the threshold (%f)",
-                           client_ip, this->get_connection_id(), this->connection_state.get_stream_error_rate(),
+                           peer_ip, this->get_connection_id(), this->connection_state.get_stream_error_rate(),
                            Http2::stream_error_rate_threshold);
       err = Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM;
     }
diff --git a/proxy/http2/Http2CommonSession.h b/proxy/http2/Http2CommonSession.h
index c59a5d7..7d4066f 100644
--- a/proxy/http2/Http2CommonSession.h
+++ b/proxy/http2/Http2CommonSession.h
@@ -23,6 +23,8 @@
 
 #pragma once
 
+#include <unordered_set>
+
 #include "HTTP2.h"
 #include "ProxySession.h"
 #include "Http2ConnectionState.h"
diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc
index 95be51d..6e8cc20 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -22,6 +22,7 @@
  */
 
 #include "P_Net.h"
+#include "HTTP2.h"
 #include "Http2ConnectionState.h"
 #include "Http2ClientSession.h"
 #include "Http2Stream.h"
@@ -29,6 +30,7 @@
 #include "Http2DebugNames.h"
 #include "HttpDebugNames.h"
 
+#include "tscore/ink_assert.h"
 #include "tscpp/util/PostScript.h"
 #include "tscpp/util/LocalBuffer.h"
 
@@ -133,7 +135,7 @@
 
   stream->increment_data_length(payload_length - pad_length - nbytes);
   if (frame.header().flags & HTTP2_FLAGS_DATA_END_STREAM) {
-    stream->recv_end_stream = true;
+    stream->receive_end_stream = true;
     if (!stream->change_state(frame.header().type, frame.header().flags)) {
       this->send_rst_stream_frame(id, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED);
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
@@ -156,23 +158,24 @@
   }
 
   // Check whether Window Size is acceptable
-  if (!this->_server_rwnd_is_shrinking && this->server_rwnd() < payload_length) {
+  if (!this->_local_rwnd_is_shrinking_in && this->get_local_rwnd_in() < payload_length) {
     return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
                       "recv data cstate.server_rwnd < payload_length");
   }
-  if (stream->server_rwnd() < payload_length) {
+  if (stream->get_local_rwnd() < payload_length) {
     return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
                       "recv data stream->server_rwnd < payload_length");
   }
 
   // Update Window size
-  this->decrement_server_rwnd(payload_length);
-  stream->decrement_server_rwnd(payload_length);
+  this->decrement_local_rwnd_in(payload_length);
+  stream->decrement_local_rwnd(payload_length);
 
   if (is_debug_tag_set("http2_con")) {
-    uint32_t rwnd = this->server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
-    Http2StreamDebug(this->session, id, "Received DATA frame: rwnd con=%zd/%" PRId32 " stream=%zd/%" PRId32, this->server_rwnd(),
-                     rwnd, stream->server_rwnd(), rwnd);
+    uint32_t const stream_window  = this->acknowledged_local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
+    uint32_t const session_window = this->_get_configured_receive_session_window_size_in();
+    Http2StreamDebug(this->session, id, "Received DATA frame: rwnd con=%zd/%" PRId32 " stream=%zd/%" PRId32,
+                     this->get_local_rwnd_in(), session_window, stream->get_local_rwnd(), stream_window);
   }
 
   const uint32_t unpadded_length = payload_length - pad_length;
@@ -271,7 +274,7 @@
   uint32_t header_block_fragment_length = payload_length;
 
   if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_STREAM) {
-    stream->recv_end_stream = true;
+    stream->receive_end_stream = true;
   }
 
   // NOTE: Strip padding if exists
@@ -368,8 +371,8 @@
     }
 
     stream->mark_milestone(Http2StreamMilestone::START_DECODE_HEADERS);
-    Http2ErrorCode result =
-      stream->decode_header_blocks(*this->local_hpack_handle, this->server_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
+    Http2ErrorCode result = stream->decode_header_blocks(*this->local_hpack_handle,
+                                                         this->acknowledged_local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
 
     if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
       if (result == Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR) {
@@ -385,7 +388,7 @@
     }
 
     // Check Content-Length & payload length when END_STREAM flag is true
-    if (stream->recv_end_stream && !stream->payload_length_is_valid()) {
+    if (stream->receive_end_stream && !stream->payload_length_is_valid()) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
                         "recv data bad payload length");
     }
@@ -429,7 +432,7 @@
 
   // If a PRIORITY frame is received with a stream identifier of 0x0, the
   // recipient MUST respond with a connection error of type PROTOCOL_ERROR.
-  if (stream_id == 0) {
+  if (stream_id == HTTP2_CONNECTION_CONTROL_STREAM) {
     return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
                       "priority 0 stream_id");
   }
@@ -491,7 +494,7 @@
 
     // Restrict number of inactive node in dependency tree smaller than max_concurrent_streams.
     // Current number of inactive node is size of tree minus active node count.
-    if (Http2::max_concurrent_streams_in > this->dependency_tree->size() - this->get_client_stream_count() + 1) {
+    if (Http2::max_concurrent_streams_in > this->dependency_tree->size() - this->get_peer_stream_count() + 1) {
       this->dependency_tree->add(priority.stream_dependency, stream_id, priority.weight, priority.exclusive_flag, nullptr);
     }
   }
@@ -513,7 +516,7 @@
   // frame is received with a stream identifier of 0x0, the recipient MUST
   // treat this as a connection error (Section 5.4.1) of type
   // PROTOCOL_ERROR.
-  if (stream_id == 0) {
+  if (stream_id == HTTP2_CONNECTION_CONTROL_STREAM) {
     return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
                       "reset access stream with invalid id");
   }
@@ -598,6 +601,7 @@
   // error of type FRAME_SIZE_ERROR.
   if (frame.header().flags & HTTP2_FLAGS_SETTINGS_ACK) {
     if (frame.header().length == 0) {
+      this->_process_incoming_settings_ack_frame();
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
     } else {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_FRAME_SIZE_ERROR,
@@ -646,10 +650,10 @@
     // windows that it maintains by the difference between the new value and
     // the old value.
     if (param.id == HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) {
-      this->update_initial_rwnd(param.value);
+      this->update_initial_peer_rwnd_in(param.value);
     }
 
-    this->client_settings.set(static_cast<Http2SettingsIdentifier>(param.id), param.value);
+    this->peer_settings.set(static_cast<Http2SettingsIdentifier>(param.id), param.value);
     ++n_settings;
   }
 
@@ -666,7 +670,7 @@
 
   // [RFC 7540] 6.5. Once all values have been applied, the recipient MUST
   // immediately emit a SETTINGS frame with the ACK flag set.
-  Http2SettingsFrame ack_frame(0, HTTP2_FLAGS_SETTINGS_ACK);
+  Http2SettingsFrame ack_frame(HTTP2_CONNECTION_CONTROL_STREAM, HTTP2_FLAGS_SETTINGS_ACK);
   this->session->xmit(ack_frame);
 
   return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
@@ -787,7 +791,7 @@
   // A receiver MUST treat the receipt of a WINDOW_UPDATE frame with a flow
   // control window increment of 0 as a connection error of type PROTOCOL_ERROR;
   if (size == 0) {
-    if (stream_id == 0) {
+    if (stream_id == HTTP2_CONNECTION_CONTROL_STREAM) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
                         "window update length=0 and id=0");
     } else {
@@ -796,10 +800,10 @@
     }
   }
 
-  if (stream_id == 0) {
+  if (stream_id == HTTP2_CONNECTION_CONTROL_STREAM) {
     // Connection level window update
     Http2StreamDebug(this->session, stream_id, "Received WINDOW_UPDATE frame - updated to: %zd delta: %u",
-                     (this->client_rwnd() + size), size);
+                     (this->get_peer_rwnd_in() + size), size);
 
     // A sender MUST NOT allow a flow-control window to exceed 2^31-1
     // octets.  If a sender receives a WINDOW_UPDATE that causes a flow-
@@ -808,12 +812,12 @@
     // sends a RST_STREAM with an error code of FLOW_CONTROL_ERROR; for the
     // connection, a GOAWAY frame with an error code of FLOW_CONTROL_ERROR
     // is sent.
-    if (size > HTTP2_MAX_WINDOW_SIZE - this->client_rwnd()) {
+    if (size > HTTP2_MAX_WINDOW_SIZE - this->get_peer_rwnd_in()) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
                         "window update too big");
     }
 
-    auto error = this->increment_client_rwnd(size);
+    auto error = this->increment_peer_rwnd_in(size);
     if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, error, "Erroneous client window update");
     }
@@ -833,7 +837,7 @@
     }
 
     Http2StreamDebug(this->session, stream_id, "Received WINDOW_UPDATE frame - updated to: %zd delta: %u",
-                     (stream->client_rwnd() + size), size);
+                     (stream->get_peer_rwnd() + size), size);
 
     // A sender MUST NOT allow a flow-control window to exceed 2^31-1
     // octets.  If a sender receives a WINDOW_UPDATE that causes a flow-
@@ -842,17 +846,17 @@
     // sends a RST_STREAM with an error code of FLOW_CONTROL_ERROR; for the
     // connection, a GOAWAY frame with an error code of FLOW_CONTROL_ERROR
     // is sent.
-    if (size > HTTP2_MAX_WINDOW_SIZE - stream->client_rwnd()) {
+    if (size > HTTP2_MAX_WINDOW_SIZE - stream->get_peer_rwnd()) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
                         "window update too big 2");
     }
 
-    auto error = stream->increment_client_rwnd(size);
+    auto error = stream->increment_peer_rwnd(size);
     if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, error);
     }
 
-    ssize_t wnd = std::min(this->client_rwnd(), stream->client_rwnd());
+    ssize_t wnd = std::min(this->get_peer_rwnd_in(), stream->get_peer_rwnd());
     if (!stream->is_closed() && stream->get_state() == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE && wnd > 0) {
       SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread());
       stream->restart_sending();
@@ -932,8 +936,8 @@
                         "continuation no state change");
     }
 
-    Http2ErrorCode result =
-      stream->decode_header_blocks(*this->local_hpack_handle, this->server_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
+    Http2ErrorCode result = stream->decode_header_blocks(*this->local_hpack_handle,
+                                                         this->acknowledged_local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
 
     if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
       if (result == Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR) {
@@ -949,7 +953,7 @@
     }
 
     // Check Content-Length & payload length when END_STREAM flag is true
-    if (stream->recv_end_stream && !stream->payload_length_is_valid()) {
+    if (stream->receive_end_stream && !stream->payload_length_is_valid()) {
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
                         "recv data bad payload length");
     }
@@ -990,7 +994,7 @@
 Http2ConnectionSettings::settings_from_configs()
 {
   settings[indexof(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)] = Http2::max_concurrent_streams_in;
-  settings[indexof(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)]    = Http2::initial_window_size;
+  settings[indexof(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)]    = Http2::initial_window_size_in;
   settings[indexof(HTTP2_SETTINGS_MAX_FRAME_SIZE)]         = Http2::max_frame_size;
   settings[indexof(HTTP2_SETTINGS_HEADER_TABLE_SIZE)]      = Http2::header_table_size;
   settings[indexof(HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)]   = Http2::max_header_list_size;
@@ -1039,21 +1043,22 @@
 void
 Http2ConnectionState::init(Http2CommonSession *ssn)
 {
-  session = ssn;
+  session                                  = ssn;
+  uint32_t const configured_session_window = this->_get_configured_receive_session_window_size_in();
 
-  if (Http2::initial_window_size < HTTP2_INITIAL_WINDOW_SIZE) {
+  if (configured_session_window < HTTP2_INITIAL_WINDOW_SIZE) {
     // There is no HTTP/2 specified way to shrink the connection window size
     // other than to receive data and not send WINDOW_UPDATE frames for a
     // while.
-    this->_server_rwnd              = HTTP2_INITIAL_WINDOW_SIZE;
-    this->_server_rwnd_is_shrinking = true;
+    this->_local_rwnd_in              = HTTP2_INITIAL_WINDOW_SIZE;
+    this->_local_rwnd_is_shrinking_in = true;
   } else {
-    this->_server_rwnd              = Http2::initial_window_size;
-    this->_server_rwnd_is_shrinking = false;
+    this->_local_rwnd_in              = configured_session_window;
+    this->_local_rwnd_is_shrinking_in = false;
   }
 
-  local_hpack_handle  = new HpackHandle(HTTP2_HEADER_TABLE_SIZE);
-  remote_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE);
+  local_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE);
+  peer_hpack_handle  = new HpackHandle(HTTP2_HEADER_TABLE_SIZE);
   if (Http2::stream_priority_enabled) {
     dependency_tree = new DependencyTree(Http2::max_concurrent_streams_in);
   }
@@ -1081,10 +1086,23 @@
   configured_settings.settings_from_configs();
   configured_settings.set(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, _adjust_concurrent_stream());
 
+  if (this->_has_dynamic_stream_window()) {
+    // Since this is the beginning of the connection and there are no streams
+    // yet, we can just set the stream window size to fill the entire session
+    // window size.
+    uint32_t const stream_window = this->_get_configured_receive_session_window_size_in();
+    configured_settings.set(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, stream_window);
+  }
+
   send_settings_frame(configured_settings);
 
-  if (server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) > HTTP2_INITIAL_WINDOW_SIZE) {
-    send_window_update_frame(0, server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - HTTP2_INITIAL_WINDOW_SIZE);
+  // If the session window size is non-default, send a WINDOW_UPDATE right
+  // away. Note that there is no session window size setting in HTTP/2. The
+  // session window size is controlled entirely by WINDOW_UPDATE frames.
+  if (this->_get_configured_receive_session_window_size_in() > HTTP2_INITIAL_WINDOW_SIZE) {
+    auto const diff = this->_get_configured_receive_session_window_size_in() - HTTP2_INITIAL_WINDOW_SIZE;
+    Http2ConDebug(session, "Updating the session window with a WINDOW_UPDATE frame: %u", diff);
+    send_window_update_frame(HTTP2_CONNECTION_CONTROL_STREAM, diff);
   }
 }
 
@@ -1107,8 +1125,8 @@
 
   delete local_hpack_handle;
   local_hpack_handle = nullptr;
-  delete remote_hpack_handle;
-  remote_hpack_handle = nullptr;
+  delete peer_hpack_handle;
+  peer_hpack_handle = nullptr;
   delete dependency_tree;
   dependency_tree = nullptr;
   this->session   = nullptr;
@@ -1302,7 +1320,7 @@
     return nullptr;
   }
 
-  bool client_streamid = http2_is_client_streamid(new_id);
+  bool is_client_streamid = http2_is_client_streamid(new_id);
 
   // 5.1.1 The identifier of a newly established stream MUST be numerically
   // greater than all streams that the initiating endpoint has opened or
@@ -1310,7 +1328,7 @@
   // and streams that are reserved using PUSH_PROMISE.  An endpoint that
   // receives an unexpected stream identifier MUST respond with a
   // connection error (Section 5.4.1) of type PROTOCOL_ERROR.
-  if (client_streamid) {
+  if (is_client_streamid) {
     if (new_id <= latest_streamid_in) {
       error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
                          "recv headers new client id less than latest stream id");
@@ -1327,15 +1345,15 @@
   // Endpoints MUST NOT exceed the limit set by their peer.  An endpoint
   // that receives a HEADERS frame that causes their advertised concurrent
   // stream limit to be exceeded MUST treat this as a stream error.
-  if (client_streamid) {
-    if (client_streams_in_count >= server_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) {
+  if (is_client_streamid) {
+    if (peer_streams_count_in >= acknowledged_local_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) {
       HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN, this_ethread());
       error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_REFUSED_STREAM,
                          "recv headers creating inbound stream beyond max_concurrent limit");
       return nullptr;
     }
   } else {
-    if (client_streams_out_count >= client_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) {
+    if (peer_streams_count_out >= peer_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) {
       HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_OUT, this_ethread());
       error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_REFUSED_STREAM,
                          "recv headers creating outbound stream beyond max_concurrent limit");
@@ -1343,31 +1361,52 @@
     }
   }
 
+  uint32_t initial_stream_window        = this->acknowledged_local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
+  uint32_t initial_stream_window_target = initial_stream_window;
+  if (is_client_streamid && this->_has_dynamic_stream_window()) {
+    // For dynamic stream windows, the peer's idea of what the window size is
+    // may be different than what we are configuring. Our calulated server
+    // receive window is always maintained at what the peer has acknowledged so
+    // far. This prevents us from enforcing window sizes that have been
+    // adjusted by SETTINGS frames which the peer has not received yet. So we
+    // initialize the receive window to what the peer has acknowledged while in
+    // the meantime calculating initial_stream_window_target for the SETTINGS
+    // frame which will shrink or enlarge it to our new desired size.
+    //
+    // The situation of dynamic stream window sizes is described in [RFC 9113]
+    // 6.9.3.
+    initial_stream_window_target = this->_get_configured_receive_session_window_size_in() / (peer_streams_count_in.load() + 1);
+  }
   Http2Stream *new_stream = THREAD_ALLOC_INIT(http2StreamAllocator, this_ethread(), session->get_proxy_session(), new_id,
-                                              client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE));
+                                              peer_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE), initial_stream_window);
 
   ink_assert(nullptr != new_stream);
   ink_assert(!stream_list.in(new_stream));
 
+  new_stream->mutex                     = new_ProxyMutex();
+  new_stream->is_first_transaction_flag = get_stream_requests() == 0;
+
   stream_list.enqueue(new_stream);
-  if (client_streamid) {
+  if (is_client_streamid) {
     latest_streamid_in = new_id;
-    ink_assert(client_streams_in_count < UINT32_MAX);
-    ++client_streams_in_count;
+    ink_assert(peer_streams_count_in < UINT32_MAX);
+    ++peer_streams_count_in;
+    if (this->_has_dynamic_stream_window()) {
+      Http2ConnectionSettings new_settings = local_settings;
+      new_settings.set(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, initial_stream_window_target);
+      send_settings_frame(new_settings);
+    }
   } else {
     latest_streamid_out = new_id;
-    ink_assert(client_streams_out_count < UINT32_MAX);
-    ++client_streams_out_count;
+    ink_assert(peer_streams_count_out < UINT32_MAX);
+    ++peer_streams_count_out;
   }
-  ++total_client_streams_count;
+  ++total_peer_streams_count;
 
   if (zombie_event != nullptr) {
     zombie_event->cancel();
     zombie_event = nullptr;
   }
-
-  new_stream->mutex                     = new_ProxyMutex();
-  new_stream->is_first_transaction_flag = get_stream_requests() == 0;
   increment_stream_requests();
 
   return new_stream;
@@ -1398,7 +1437,7 @@
     static uint16_t starting_point = 0;
 
     // Change the start point randomly
-    for (int i = starting_point % total_client_streams_count; i >= 0; --i) {
+    for (int i = starting_point % total_peer_streams_count; i >= 0; --i) {
       end = static_cast<Http2Stream *>(end->link.next ? end->link.next : stream_list.head);
     }
     s = static_cast<Http2Stream *>(end->link.next ? end->link.next : stream_list.head);
@@ -1407,7 +1446,7 @@
     while (s != end) {
       Http2Stream *next = static_cast<Http2Stream *>(s->link.next ? s->link.next : stream_list.head);
       if (!s->is_closed() && s->get_state() == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE &&
-          std::min(this->client_rwnd(), s->client_rwnd()) > 0) {
+          std::min(this->get_peer_rwnd_in(), s->get_peer_rwnd()) > 0) {
         SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
         s->restart_sending();
       }
@@ -1415,7 +1454,7 @@
       s = next;
     }
     if (!s->is_closed() && s->get_state() == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE &&
-        std::min(this->client_rwnd(), s->client_rwnd()) > 0) {
+        std::min(this->get_peer_rwnd_in(), s->get_peer_rwnd()) > 0) {
       SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
       s->restart_sending();
     }
@@ -1427,31 +1466,51 @@
 void
 Http2ConnectionState::restart_receiving(Http2Stream *stream)
 {
-  uint32_t initial_rwnd = this->server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
-  uint32_t min_rwnd     = std::min(initial_rwnd, this->server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE));
-
   // Connection level WINDOW UPDATE
-  if (this->server_rwnd() < min_rwnd) {
-    Http2WindowSize diff_size = initial_rwnd - this->server_rwnd();
-    this->increment_server_rwnd(diff_size);
-    this->_server_rwnd_is_shrinking = false;
-    this->send_window_update_frame(0, diff_size);
+  uint32_t const configured_session_window = this->_get_configured_receive_session_window_size_in();
+  uint32_t const min_session_window =
+    std::min(configured_session_window, this->acknowledged_local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE));
+  if (this->get_local_rwnd_in() < min_session_window) {
+    Http2WindowSize diff_size = configured_session_window - this->get_local_rwnd_in();
+    if (diff_size > 0) {
+      this->increment_local_rwnd_in(diff_size);
+      this->_local_rwnd_is_shrinking_in = false;
+      this->send_window_update_frame(HTTP2_CONNECTION_CONTROL_STREAM, diff_size);
+    }
   }
 
   // Stream level WINDOW UPDATE
-  if (stream == nullptr || stream->server_rwnd() >= min_rwnd) {
+  if (stream == nullptr || stream->get_local_rwnd() >= min_session_window) {
+    // There's no need to increase the stream window size if it is already big
+    // enough to hold what the stream/max frame size can receive.
     return;
   }
 
   // If read_vio is buffering data, do not fully update window
-  int64_t data_size = stream->read_vio_read_avail();
-  if (data_size >= initial_rwnd) {
+  uint32_t const initial_stream_window = this->acknowledged_local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
+  int64_t data_size                    = stream->read_vio_read_avail();
+  if (data_size >= initial_stream_window) {
     return;
   }
 
-  Http2WindowSize diff_size = initial_rwnd - std::max(static_cast<int64_t>(stream->server_rwnd()), data_size);
-  stream->increment_server_rwnd(diff_size);
-  this->send_window_update_frame(stream->get_id(), diff_size);
+  Http2WindowSize diff_size = 0;
+  if (stream->get_local_rwnd() < 0) {
+    // Receive windows can be negative if we sent a SETTINGS frame that
+    // decreased the stream window size mid-stream. This is not a problem: we
+    // simply compute a WINDOW_UPDATE value to bring the window up to the
+    // target initial_stream_window size.
+    diff_size = initial_stream_window - stream->get_local_rwnd();
+  } else {
+    diff_size = initial_stream_window - std::max(static_cast<int64_t>(stream->get_local_rwnd()), data_size);
+  }
+
+  // Dynamic stream window sizes may result in negative values. In this case,
+  // we'll just be waiting for the peer to send more data until the receive
+  // window decreases to be under the initial window size.
+  if (diff_size > 0) {
+    stream->increment_local_rwnd(diff_size);
+    this->send_window_update_frame(stream->get_id(), diff_size);
+  }
 }
 
 void
@@ -1518,11 +1577,11 @@
 
   stream_list.remove(stream);
   if (http2_is_client_streamid(stream->get_id())) {
-    ink_assert(client_streams_in_count > 0);
-    --client_streams_in_count;
+    ink_assert(peer_streams_count_in > 0);
+    --peer_streams_count_in;
   } else {
-    ink_assert(client_streams_out_count > 0);
-    --client_streams_out_count;
+    ink_assert(peer_streams_count_out > 0);
+    --peer_streams_count_out;
   }
   // total_client_streams_count will be decremented in release_stream(), because it's a counter include streams in the process of
   // shutting down.
@@ -1541,7 +1600,7 @@
   if (this->session) {
     ink_assert(this->mutex == session->get_mutex());
 
-    if (total_client_streams_count == 0) {
+    if (total_peer_streams_count == 0) {
       if (fini_received) {
         session->do_clear_session_active();
 
@@ -1572,12 +1631,44 @@
 }
 
 void
-Http2ConnectionState::update_initial_rwnd(Http2WindowSize new_size)
+Http2ConnectionState::update_initial_peer_rwnd_in(Http2WindowSize new_size)
 {
   // Update stream level window sizes
   for (Http2Stream *s = stream_list.head; s; s = static_cast<Http2Stream *>(s->link.next)) {
     SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
-    s->update_initial_rwnd(new_size - (client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->client_rwnd()));
+    // Set the new window size, but take into account the already adjusted
+    // window based on previously sent bytes.
+    //
+    // For example:
+    // 1. Client initializes the stream window to 10K bytes.
+    // 2. ATS sends 3K bytes to the client. The stream window is now 7K bytes.
+    // 3. The client sends a SETTINGS frame to update the initial window size to 20K bytes.
+    // 4. ATS should update the stream window to 17K bytes: 20K - (10K - 7K).
+    //
+    // Note that if the client reduces the stream window, this may result in
+    // negative receive window values.
+    s->set_peer_rwnd(new_size - (peer_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->get_peer_rwnd()));
+  }
+}
+
+void
+Http2ConnectionState::update_initial_local_rwnd_in(Http2WindowSize new_size)
+{
+  // Update stream level window sizes
+  for (Http2Stream *s = stream_list.head; s; s = static_cast<Http2Stream *>(s->link.next)) {
+    SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
+    // Set the new window size, but take into account the already adjusted
+    // window based on previously sent bytes.
+    //
+    // For example:
+    // 1. ATS initializes the stream window to 10K bytes.
+    // 2. ATS receives 3K bytes from the client. The stream window is now 7K bytes.
+    // 3. ATS sends a SETTINGS frame to the client to update the initial window size to 20K bytes.
+    // 4. The stream window should be updated to 17K bytes: 20K - (10K - 7K).
+    //
+    // Note that if ATS reduces the stream window, this may result in negative
+    // receive window values.
+    s->set_local_rwnd(new_size - (acknowledged_local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->get_local_rwnd()));
   }
 }
 
@@ -1606,7 +1697,7 @@
   Http2DependencyTree::Node *node = dependency_tree->top();
 
   // No node to send or no connection level window left
-  if (node == nullptr || _client_rwnd <= 0) {
+  if (node == nullptr || _peer_rwnd_in <= 0) {
     return;
   }
 
@@ -1626,7 +1717,7 @@
       dependency_tree->update(node, len);
 
       SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());
-      stream->signal_write_event(true);
+      stream->signal_write_event(Http2Stream::CALL_UPDATE);
     }
     break;
   }
@@ -1648,13 +1739,13 @@
 Http2SendDataFrameResult
 Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_length)
 {
-  const ssize_t window_size         = std::min(this->client_rwnd(), stream->client_rwnd());
+  const ssize_t window_size         = std::min(this->get_peer_rwnd_in(), stream->get_peer_rwnd());
   const size_t buf_len              = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_DATA]);
   const size_t write_available_size = std::min(buf_len, static_cast<size_t>(window_size));
   payload_length                    = 0;
 
   uint8_t flags               = 0x00;
-  IOBufferReader *resp_reader = stream->response_get_data_reader();
+  IOBufferReader *resp_reader = stream->get_data_reader_for_send();
 
   SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());
 
@@ -1703,12 +1794,12 @@
   }
 
   // Update window size
-  this->decrement_client_rwnd(payload_length);
-  stream->decrement_client_rwnd(payload_length);
+  this->decrement_peer_rwnd_in(payload_length);
+  stream->decrement_peer_rwnd(payload_length);
 
   // Create frame
   Http2StreamDebug(session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd",
-                   _client_rwnd, stream->client_rwnd(), payload_length);
+                   _peer_rwnd_in, stream->get_peer_rwnd(), payload_length);
 
   Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length);
   this->session->xmit(data, flags & HTTP2_FLAGS_DATA_END_STREAM);
@@ -1764,7 +1855,7 @@
 
   Http2StreamDebug(session, stream->get_id(), "Send HEADERS frame");
 
-  HTTPHdr *resp_hdr = &stream->response_header;
+  HTTPHdr *resp_hdr = &stream->_send_header;
   http2_convert_header_from_1_1_to_2(resp_hdr);
 
   uint32_t buf_len = resp_hdr->length_get() * 2; // Make it double just in case
@@ -1772,8 +1863,8 @@
   uint8_t *buf = local_buffer.data();
 
   stream->mark_milestone(Http2StreamMilestone::START_ENCODE_HEADERS);
-  Http2ErrorCode result = http2_encode_header_blocks(resp_hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle),
-                                                     client_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
+  Http2ErrorCode result = http2_encode_header_blocks(resp_hdr, buf, buf_len, &header_blocks_size, *(this->peer_hpack_handle),
+                                                     peer_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
   if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
     return;
   }
@@ -1832,7 +1923,7 @@
   int payload_length          = 0;
   uint8_t flags               = 0x00;
 
-  if (client_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) {
+  if (peer_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) {
     return false;
   }
 
@@ -1862,8 +1953,8 @@
   ts::LocalBuffer local_buffer(buf_len);
   uint8_t *buf = local_buffer.data();
 
-  Http2ErrorCode result = http2_encode_header_blocks(&hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle),
-                                                     client_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
+  Http2ErrorCode result = http2_encode_header_blocks(&hdr, buf, buf_len, &header_blocks_size, *(this->peer_hpack_handle),
+                                                     peer_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
   if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
     return false;
   }
@@ -1922,9 +2013,9 @@
     }
   }
   stream->change_state(HTTP2_FRAME_TYPE_PUSH_PROMISE, HTTP2_FLAGS_PUSH_PROMISE_END_HEADERS);
-  stream->set_request_headers(hdr);
+  stream->set_receive_headers(hdr);
   stream->new_transaction();
-  stream->recv_end_stream = true; // No more data with the request
+  stream->receive_end_stream = true; // No more data with the request
   stream->send_request(*this);
 
   return true;
@@ -1962,7 +2053,7 @@
 void
 Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_settings)
 {
-  const Http2StreamId stream_id = 0;
+  constexpr Http2StreamId stream_id = HTTP2_CONNECTION_CONTROL_STREAM;
 
   Http2StreamDebug(session, stream_id, "Send SETTINGS frame");
 
@@ -1971,24 +2062,60 @@
 
   for (int i = HTTP2_SETTINGS_HEADER_TABLE_SIZE; i < HTTP2_SETTINGS_MAX; ++i) {
     Http2SettingsIdentifier id = static_cast<Http2SettingsIdentifier>(i);
-    unsigned settings_value    = new_settings.get(id);
+    unsigned const old_value   = local_settings.get(id);
+    unsigned const new_value   = new_settings.get(id);
 
     // Send only difference
-    if (settings_value != server_settings.get(id)) {
-      Http2StreamDebug(session, stream_id, "  %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value);
+    if (new_value != old_value) {
+      Http2StreamDebug(session, stream_id, "  %s : %u -> %u", Http2DebugNames::get_settings_param_name(id), old_value, new_value);
 
-      params[params_size++] = {static_cast<uint16_t>(id), settings_value};
+      params[params_size++] = {static_cast<uint16_t>(id), new_value};
 
       // Update current settings
-      server_settings.set(id, new_settings.get(id));
+      local_settings.set(id, new_settings.get(id));
     }
   }
 
   Http2SettingsFrame settings(stream_id, HTTP2_FRAME_NO_FLAG, params, params_size);
+
+  this->_outstanding_settings_frames_in.emplace(new_settings);
   this->session->xmit(settings);
 }
 
 void
+Http2ConnectionState::_process_incoming_settings_ack_frame()
+{
+  constexpr Http2StreamId stream_id = HTTP2_CONNECTION_CONTROL_STREAM;
+  Http2StreamDebug(session, stream_id, "Processing SETTINGS ACK frame with a queue size of %zu",
+                   this->_outstanding_settings_frames_in.size());
+
+  // Do not update this->acknowledged_local_settings yet as
+  // update_initial_server_rwnd relies upon it still pointing to the old value.
+  Http2ConnectionSettings const &old_settings = this->acknowledged_local_settings;
+  Http2ConnectionSettings const &new_settings = this->_outstanding_settings_frames_in.front().get_outstanding_settings();
+
+  for (int i = HTTP2_SETTINGS_HEADER_TABLE_SIZE; i < HTTP2_SETTINGS_MAX; ++i) {
+    Http2SettingsIdentifier id = static_cast<Http2SettingsIdentifier>(i);
+    unsigned const old_value   = old_settings.get(id);
+    unsigned const new_value   = new_settings.get(id);
+
+    if (new_value == old_value) {
+      continue;
+    }
+
+    Http2StreamDebug(session, stream_id, "SETTINGS ACK %s : %u -> %u", Http2DebugNames::get_settings_param_name(id), old_value,
+                     new_value);
+
+    if (id == HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) {
+      // Update all the streams for the newly acknowledged window size.
+      this->update_initial_local_rwnd_in(new_value);
+    }
+  }
+  this->acknowledged_local_settings = new_settings;
+  this->_outstanding_settings_frames_in.pop();
+}
+
+void
 Http2ConnectionState::send_ping_frame(Http2StreamId id, uint8_t flag, const uint8_t *opaque_data)
 {
   Http2StreamDebug(session, id, "Send PING frame");
@@ -2025,6 +2152,9 @@
 {
   Http2StreamDebug(session, id, "Send WINDOW_UPDATE frame: size=%" PRIu32, size);
 
+  // By specification, the window update increment must be greater than 0.
+  ink_release_assert(size > 0);
+
   // Create WINDOW_UPDATE frame
   Http2WindowUpdateFrame window_update(id, size);
   this->session->xmit(window_update);
@@ -2111,16 +2241,46 @@
   return Http2::max_concurrent_streams_in;
 }
 
-ssize_t
-Http2ConnectionState::client_rwnd() const
+uint32_t
+Http2ConnectionState::_get_configured_receive_session_window_size_in() const
 {
-  return this->_client_rwnd;
+  switch (Http2::flow_control_policy_in) {
+  case Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM:
+    return Http2::initial_window_size_in;
+  case Http2FlowControlPolicy::LARGE_SESSION_AND_STATIC_STREAM:
+  case Http2FlowControlPolicy::LARGE_SESSION_AND_DYNAMIC_STREAM:
+    return Http2::initial_window_size_in * Http2::max_concurrent_streams_in;
+  }
+
+  // This is unreachable, but adding a return here quiets a compiler warning.
+  return Http2::initial_window_size_in;
+}
+
+bool
+Http2ConnectionState::_has_dynamic_stream_window() const
+{
+  switch (Http2::flow_control_policy_in) {
+  case Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM:
+  case Http2FlowControlPolicy::LARGE_SESSION_AND_STATIC_STREAM:
+    return false;
+  case Http2FlowControlPolicy::LARGE_SESSION_AND_DYNAMIC_STREAM:
+    return true;
+  }
+
+  // This is unreachable, but adding a return here quiets a compiler warning.
+  return false;
+}
+
+ssize_t
+Http2ConnectionState::get_peer_rwnd_in() const
+{
+  return this->_peer_rwnd_in;
 }
 
 Http2ErrorCode
-Http2ConnectionState::increment_client_rwnd(size_t amount)
+Http2ConnectionState::increment_peer_rwnd_in(size_t amount)
 {
-  this->_client_rwnd += amount;
+  this->_peer_rwnd_in += amount;
 
   this->_recent_rwnd_increment[this->_recent_rwnd_increment_index] = amount;
   ++this->_recent_rwnd_increment_index;
@@ -2135,28 +2295,28 @@
 }
 
 Http2ErrorCode
-Http2ConnectionState::decrement_client_rwnd(size_t amount)
+Http2ConnectionState::decrement_peer_rwnd_in(size_t amount)
 {
-  this->_client_rwnd -= amount;
+  this->_peer_rwnd_in -= amount;
   return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
 }
 
 ssize_t
-Http2ConnectionState::server_rwnd() const
+Http2ConnectionState::get_local_rwnd_in() const
 {
-  return this->_server_rwnd;
+  return this->_local_rwnd_in;
 }
 
 Http2ErrorCode
-Http2ConnectionState::increment_server_rwnd(size_t amount)
+Http2ConnectionState::increment_local_rwnd_in(size_t amount)
 {
-  this->_server_rwnd += amount;
+  this->_local_rwnd_in += amount;
   return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
 }
 
 Http2ErrorCode
-Http2ConnectionState::decrement_server_rwnd(size_t amount)
+Http2ConnectionState::decrement_local_rwnd_in(size_t amount)
 {
-  this->_server_rwnd -= amount;
+  this->_local_rwnd_in -= amount;
   return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
 }
diff --git a/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h
index d3f2268..ee9bc45 100644
--- a/proxy/http2/Http2ConnectionState.h
+++ b/proxy/http2/Http2ConnectionState.h
@@ -24,6 +24,7 @@
 #pragma once
 
 #include <atomic>
+#include <queue>
 
 #include "NetTimeout.h"
 
@@ -80,15 +81,35 @@
 
   ProxyError rx_error_code;
   ProxyError tx_error_code;
-  Http2CommonSession *session      = nullptr;
-  HpackHandle *local_hpack_handle  = nullptr;
-  HpackHandle *remote_hpack_handle = nullptr;
-  DependencyTree *dependency_tree  = nullptr;
+  Http2CommonSession *session     = nullptr;
+  HpackHandle *local_hpack_handle = nullptr;
+  HpackHandle *peer_hpack_handle  = nullptr;
+  DependencyTree *dependency_tree = nullptr;
   ActivityCop<Http2Stream> _cop;
 
-  // Settings.
-  Http2ConnectionSettings server_settings;
-  Http2ConnectionSettings client_settings;
+  /** The HTTP/2 settings configured by ATS and dictated to the peer via
+   * SETTINGS frames. */
+  Http2ConnectionSettings local_settings;
+
+  /** The latest set of settings that have been acknowledged by the peer.
+   *
+   * The default constructed value of this via the Http2ConnectionSettings
+   * constructor (i.e., the value before any SETTINGS ACK frames have been
+   * received) will instantiate these settings to the default HTTP/2 settings
+   * values.
+   *
+   * @note that @a local_settings are our latest configured settings which have
+   * been sent to the peer but may not be acknowledged yet. @a
+   * last_acknowledged_settings are the latest settings of which the peer has
+   * acknowledged receipt. For this reason, window enforcement behavior and
+   * WINDOW_UPDATE calculations should be based upon @a
+   * last_acknowledged_settings.
+   */
+  Http2ConnectionSettings acknowledged_local_settings;
+
+  /** The HTTP/2 settings configured by the peer and dictated to ATS via
+   * SETTINGS frames. */
+  Http2ConnectionSettings peer_settings;
 
   void init(Http2CommonSession *ssn);
   void send_connection_preface();
@@ -107,7 +128,12 @@
   void release_stream();
   void cleanup_streams();
   void restart_receiving(Http2Stream *stream);
-  void update_initial_rwnd(Http2WindowSize new_size);
+
+  /** Update all streams for the peer's newly dictated stream window size. */
+  void update_initial_peer_rwnd_in(Http2WindowSize new_size);
+
+  /** Update all streams for our newly dictated stream window size. */
+  void update_initial_local_rwnd_in(Http2WindowSize new_size);
 
   Http2StreamId get_latest_stream_id_in() const;
   Http2StreamId get_latest_stream_id_out() const;
@@ -119,8 +145,8 @@
   void set_continued_stream_id(Http2StreamId stream_id);
   void clear_continued_stream_id();
 
-  uint32_t get_client_stream_count() const;
-  void decrement_stream_count();
+  uint32_t get_peer_stream_count() const;
+  void decrement_peer_stream_count();
   double get_stream_error_rate() const;
   Http2ErrorCode get_shutdown_reason() const;
 
@@ -132,7 +158,16 @@
   void send_headers_frame(Http2Stream *stream);
   bool send_push_promise_frame(Http2Stream *stream, URL &url, const MIMEField *accept_encoding);
   void send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec);
+
+  /** Send a SETTINGS frame to the peer.
+   *
+   * local_settings is updated to the value of @a new_settings as a byproduct
+   * of this call.
+   *
+   * @param[in] new_settings The settings to send to the peer.
+   */
   void send_settings_frame(const Http2ConnectionSettings &new_settings);
+
   void send_ping_frame(Http2StreamId id, uint8_t flag, const uint8_t *opaque_data);
   void send_goaway_frame(Http2StreamId id, Http2ErrorCode ec);
   void send_window_update_frame(Http2StreamId id, uint32_t size);
@@ -156,12 +191,12 @@
   void increment_received_priority_frame_count();
   uint32_t get_received_priority_frame_count();
 
-  ssize_t client_rwnd() const;
-  Http2ErrorCode increment_client_rwnd(size_t amount);
-  Http2ErrorCode decrement_client_rwnd(size_t amount);
-  ssize_t server_rwnd() const;
-  Http2ErrorCode increment_server_rwnd(size_t amount);
-  Http2ErrorCode decrement_server_rwnd(size_t amount);
+  ssize_t get_peer_rwnd_in() const;
+  Http2ErrorCode increment_peer_rwnd_in(size_t amount);
+  Http2ErrorCode decrement_peer_rwnd_in(size_t amount);
+  ssize_t get_local_rwnd_in() const;
+  Http2ErrorCode increment_local_rwnd_in(size_t amount);
+  Http2ErrorCode decrement_local_rwnd_in(size_t amount);
 
 private:
   Http2Error rcv_data_frame(const Http2Frame &);
@@ -191,6 +226,25 @@
 
   unsigned _adjust_concurrent_stream();
 
+  /** Receive and process a SETTINGS frame with the ACK flag set.
+   *
+   * This function will process any settings updates that have now been
+   * acknowleged by the peer.
+   */
+  void _process_incoming_settings_ack_frame();
+
+  /** Calculate the initial session window size that we communicate to peers.
+   *
+   * @return The initial receive window size.
+   */
+  uint32_t _get_configured_receive_session_window_size_in() const;
+
+  /** Whether our stream window can change over the lifetime of a session.
+   *
+   * @return @c true if the stream window can change, @c false otherwise.
+   */
+  bool _has_dynamic_stream_window() const;
+
   // NOTE: 'stream_list' has only active streams.
   //   If given Stream Identifier is not found in stream_list and it is less
   //   than or equal to latest_streamid_in, the state of Stream
@@ -202,24 +256,45 @@
   Http2StreamId latest_streamid_out = 0;
   std::atomic<int> stream_requests  = 0;
 
-  // Counter for current active streams which is started by client
-  std::atomic<uint32_t> client_streams_in_count = 0;
+  // Counter for current active streams which are started by the client.
+  std::atomic<uint32_t> peer_streams_count_in = 0;
 
-  // Counter for current active streams which is started by server
-  std::atomic<uint32_t> client_streams_out_count = 0;
+  // Counter for current active streams which are started by the origin.
+  std::atomic<uint32_t> peer_streams_count_out = 0;
 
-  // Counter for current active streams and streams in the process of shutting down
-  std::atomic<uint32_t> total_client_streams_count = 0;
+  // Counter for current active streams (from either the client or origin side)
+  // and streams in the process of shutting down
+  std::atomic<uint32_t> total_peer_streams_count = 0;
 
   // Counter for stream errors ATS sent
   uint32_t stream_error_count = 0;
 
   // Connection level window size
-  ssize_t _client_rwnd = HTTP2_INITIAL_WINDOW_SIZE;
-  ssize_t _server_rwnd = 0;
 
-  /** Whether the session window is in a shrinking state before we send the
-   * first WINDOW_UPDATE frame.
+  /** The client-side session level window that we have to respect when we send
+   * data to the peer.
+   *
+   * This is the session window configured by the peer via WINDOW_UPDATE
+   * frames. Per specification, this defaults to HTTP2_INITIAL_WINDOW_SIZE (see
+   * RFC 9113, section 6.9.2). As we send data, we decrement this value. If it
+   * reaches zero, we stop sending data to respect the peer's flow control
+   * specification. When we receive WINDOW_UPDATE frames, we increment this
+   * value.
+   */
+  ssize_t _peer_rwnd_in = HTTP2_INITIAL_WINDOW_SIZE;
+
+  /** The session window we maintain with the client-side peer via
+   * WINDOW_UPDATE frames.
+   *
+   * We maintain the window we expect the peer to respect by sending
+   * WINDOW_UPDATE frames to the peer. As we receive data, we decrement this
+   * value, as we send WINDOW_UPDATE frames, we increment it. If it reaches
+   * zero, we generate a connection-level error.
+   */
+  ssize_t _local_rwnd_in = 0;
+
+  /** Whether the client-side session window is in a shrinking state before we
+   * send the first WINDOW_UPDATE frame.
    *
    * Unlike HTTP/2 streams, the HTTP/2 session window has no way to initialize
    * it to a value lower than 65,535. If the initial value is lower than
@@ -228,7 +303,7 @@
    * window gets to the desired size, we start maintaining the window via
    * WINDOW_UPDATE frames.
    */
-  bool _server_rwnd_is_shrinking = false;
+  bool _local_rwnd_is_shrinking_in = false;
 
   std::array<size_t, 5> _recent_rwnd_increment = {SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX};
   int _recent_rwnd_increment_index             = 0;
@@ -238,6 +313,54 @@
   Http2FrequencyCounter _received_ping_frame_counter;
   Http2FrequencyCounter _received_priority_frame_counter;
 
+  /** Records the various settings for each SETTINGS frame that we've sent.
+   *
+   * There are certain SETTINGS values that we send but cannot act upon until the
+   * peer acknowledges them. For instance, we cannot enforce reduced stream
+   * window sizes until the peer acknowledges the SETTINGS frame that shrinks the
+   * size.
+   *
+   * There is an OutstandingSettingsFrame instance for each SETTINGS frame that
+   * we send. We store these in a queue and associate each SETTINGS frame with
+   * an ACK flag from the peer with a corresponding OutstandingSettingsFrame
+   * instance.
+   *
+   * For details about SETTINGS synchronization via the ACK flag, see:
+   *
+   *   [RFC 9113] 6.5.3 Settings Synchronization
+   */
+  class OutstandingSettingsFrame
+  {
+  public:
+    OutstandingSettingsFrame(const Http2ConnectionSettings &settings) : _settings(settings) {}
+
+    /** Returns the settings parameters that were configured via the SETTINGS frame
+     * associated with this instance.
+     *
+     * @return The settigns parameters that were configured at the time the
+     * associated SETTINGS frame was sent. @note that this is not just the
+     * values in the SETTINGS frame, but those values along with all the local
+     * settings that were in place but not explicitly configured via the frame.
+     * Thus this returns the snapshot of the entire set of settings configured
+     * when the SETTINGS frame was sent.
+     */
+    Http2ConnectionSettings const &
+    get_outstanding_settings() const
+    {
+      return _settings;
+    }
+
+  private:
+    /** The SETTINGS parameters that were set at the time of the associated
+     * SETTINGS frame being sent.
+     */
+    Http2ConnectionSettings const _settings;
+  };
+
+  /** The queue of SETTINGS frames that we have sent but have not yet been
+   * acknowledged by the peer. */
+  std::queue<OutstandingSettingsFrame> _outstanding_settings_frames_in;
+
   // NOTE: Id of stream which MUST receive CONTINUATION frame.
   //   - [RFC 7540] 6.2 HEADERS
   //     "A HEADERS frame without the END_HEADERS flag set MUST be followed by a
@@ -304,15 +427,15 @@
 }
 
 inline uint32_t
-Http2ConnectionState::get_client_stream_count() const
+Http2ConnectionState::get_peer_stream_count() const
 {
-  return client_streams_in_count;
+  return peer_streams_count_in;
 }
 
 inline void
-Http2ConnectionState::decrement_stream_count()
+Http2ConnectionState::decrement_peer_stream_count()
 {
-  --total_client_streams_count;
+  --total_peer_streams_count;
 }
 
 inline double
diff --git a/proxy/http2/Http2Frame.h b/proxy/http2/Http2Frame.h
index f893d11..927f5b3 100644
--- a/proxy/http2/Http2Frame.h
+++ b/proxy/http2/Http2Frame.h
@@ -210,7 +210,7 @@
 {
 public:
   Http2GoawayFrame(Http2Goaway p)
-    : Http2TxFrame({HTTP2_GOAWAY_LEN, HTTP2_FRAME_TYPE_GOAWAY, HTTP2_FRAME_NO_FLAG, HTTP2_CONNECTION_CONTROL_STRTEAM}), _params(p)
+    : Http2TxFrame({HTTP2_GOAWAY_LEN, HTTP2_FRAME_TYPE_GOAWAY, HTTP2_FRAME_NO_FLAG, HTTP2_CONNECTION_CONTROL_STREAM}), _params(p)
   {
   }
 
diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc
index 7d3a8b7..28a18e3 100644
--- a/proxy/http2/Http2Stream.cc
+++ b/proxy/http2/Http2Stream.cc
@@ -40,24 +40,21 @@
 
 ClassAllocator<Http2Stream, true> http2StreamAllocator("http2StreamAllocator");
 
-Http2Stream::Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_rwnd)
-  : super(session), _id(sid), _client_rwnd(initial_rwnd)
+Http2Stream::Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_peer_rwnd, ssize_t initial_local_rwnd)
+  : super(session), _id(sid), _peer_rwnd(initial_peer_rwnd), _local_rwnd(initial_local_rwnd)
 {
   SET_HANDLER(&Http2Stream::main_event_handler);
 
   this->mark_milestone(Http2StreamMilestone::OPEN);
 
   this->_sm                       = nullptr;
-  this->_id                       = sid;
   this->_thread                   = this_ethread();
-  this->_client_rwnd              = initial_rwnd;
-  this->_server_rwnd              = Http2::initial_window_size;
   this->upstream_outbound_options = *(session->accept_options);
 
-  this->_reader = this->_request_buffer.alloc_reader();
+  this->_reader = this->_receive_buffer.alloc_reader();
 
-  _req_header.create(HTTP_TYPE_REQUEST);
-  response_header.create(HTTP_TYPE_RESPONSE, HTTP_2_0);
+  _receive_header.create(HTTP_TYPE_REQUEST);
+  _send_header.create(HTTP_TYPE_RESPONSE, HTTP_2_0);
 
   http_parser_init(&http_parser);
 }
@@ -83,7 +80,7 @@
     // In many cases, this has been called earlier, so this call is a no-op
     h2_proxy_ssn->connection_state.delete_stream(this);
 
-    h2_proxy_ssn->connection_state.decrement_stream_count();
+    h2_proxy_ssn->connection_state.decrement_peer_stream_count();
 
     // Update session's stream counts, so it accurately goes into keep-alive state
     h2_proxy_ssn->connection_state.release_stream();
@@ -119,11 +116,11 @@
           this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::CLOSE));
   }
 
-  _req_header.destroy();
-  response_header.destroy();
+  _receive_header.destroy();
+  _send_header.destroy();
 
   // Drop references to all buffer data
-  this->_request_buffer.clear();
+  this->_receive_buffer.clear();
 
   // Free the mutexes in the VIO
   read_vio.mutex.clear();
@@ -219,8 +216,8 @@
 Http2ErrorCode
 Http2Stream::decode_header_blocks(HpackHandle &hpack_handle, uint32_t maximum_table_size)
 {
-  Http2ErrorCode error = http2_decode_header_blocks(&_req_header, header_blocks, header_blocks_length, nullptr, hpack_handle,
-                                                    trailing_header, maximum_table_size);
+  Http2ErrorCode error = http2_decode_header_blocks(&_receive_header, header_blocks, header_blocks_length, nullptr, hpack_handle,
+                                                    is_trailing_header, maximum_table_size);
   if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
     Http2StreamDebug("Error decoding header blocks: %u", static_cast<uint32_t>(error));
   }
@@ -234,10 +231,10 @@
   this->_http_sm_id = this->_sm->sm_id;
 
   // Convert header to HTTP/1.1 format
-  if (http2_convert_header_from_2_to_1_1(&_req_header) == PARSE_RESULT_ERROR) {
+  if (http2_convert_header_from_2_to_1_1(&_receive_header) == PARSE_RESULT_ERROR) {
     // There's no way to cause Bad Request directly at this time.
     // Set an invalid method so it causes an error later.
-    _req_header.method_set("\xffVOID", 1);
+    _receive_header.method_set("\xffVOID", 1);
   }
 
   // Write header to a buffer.  Borrowing logic from HttpSM::write_header_into_buffer.
@@ -248,16 +245,16 @@
   do {
     bufindex             = 0;
     tmp                  = dumpoffset;
-    IOBufferBlock *block = this->_request_buffer.get_current_block();
+    IOBufferBlock *block = this->_receive_buffer.get_current_block();
     if (!block) {
-      this->_request_buffer.add_block();
-      block = this->_request_buffer.get_current_block();
+      this->_receive_buffer.add_block();
+      block = this->_receive_buffer.get_current_block();
     }
-    done = _req_header.print(block->start(), block->write_avail(), &bufindex, &tmp);
+    done = _receive_header.print(block->start(), block->write_avail(), &bufindex, &tmp);
     dumpoffset += bufindex;
-    this->_request_buffer.fill(bufindex);
+    this->_receive_buffer.fill(bufindex);
     if (!done) {
-      this->_request_buffer.add_block();
+      this->_receive_buffer.add_block();
     }
   } while (!done);
 
@@ -268,7 +265,7 @@
 
   // Is the _sm ready to process the header?
   if (this->read_vio.nbytes > 0) {
-    if (this->recv_end_stream) {
+    if (this->receive_end_stream) {
       this->read_vio.nbytes = bufindex;
       this->signal_read_event(VC_EVENT_READ_COMPLETE);
     } else {
@@ -286,7 +283,7 @@
   switch (_state) {
   case Http2StreamState::HTTP2_STREAM_STATE_IDLE:
     if (type == HTTP2_FRAME_TYPE_HEADERS) {
-      if (recv_end_stream) {
+      if (receive_end_stream) {
         _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE;
       } else if (send_end_stream) {
         _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL;
@@ -294,7 +291,7 @@
         _state = Http2StreamState::HTTP2_STREAM_STATE_OPEN;
       }
     } else if (type == HTTP2_FRAME_TYPE_CONTINUATION) {
-      if (recv_end_stream) {
+      if (receive_end_stream) {
         _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE;
       } else if (send_end_stream) {
         _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL;
@@ -312,7 +309,7 @@
     if (type == HTTP2_FRAME_TYPE_RST_STREAM) {
       _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED;
     } else if (type == HTTP2_FRAME_TYPE_HEADERS || type == HTTP2_FRAME_TYPE_DATA) {
-      if (recv_end_stream) {
+      if (receive_end_stream) {
         _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE;
       } else if (send_end_stream) {
         _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL;
@@ -344,7 +341,7 @@
     return false;
 
   case Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL:
-    if (type == HTTP2_FRAME_TYPE_RST_STREAM || recv_end_stream) {
+    if (type == HTTP2_FRAME_TYPE_RST_STREAM || receive_end_stream) {
       _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED;
     } else {
       // Error, set state closed
@@ -418,9 +415,9 @@
   write_vio.vc_server = this;
   write_vio.op        = VIO::WRITE;
 
-  if (c != nullptr && nbytes > 0 && this->is_client_state_writeable()) {
+  if (c != nullptr && nbytes > 0 && this->is_state_writeable()) {
     update_write_request(false);
-  } else if (!this->is_client_state_writeable()) {
+  } else if (!this->is_state_writeable()) {
     // Cannot start a write on a closed stream
     return nullptr;
   }
@@ -442,7 +439,7 @@
     // by the time this is called from transaction_done.
     closed = true;
 
-    if (_proxy_ssn && this->is_client_state_writeable()) {
+    if (_proxy_ssn && this->is_state_writeable()) {
       // Make sure any trailing end of stream frames are sent
       // We will be removed at send_data_frames or closing connection phase
       Http2ClientSession *h2_proxy_ssn = static_cast<Http2ClientSession *>(this->_proxy_ssn);
@@ -591,7 +588,7 @@
 
   // Try to be smart and only signal if there was additional data
   int send_event = VC_EVENT_READ_READY;
-  if (read_vio.ntodo() == 0 || (this->recv_end_stream && this->read_vio.nbytes != INT64_MAX)) {
+  if (read_vio.ntodo() == 0 || (this->receive_end_stream && this->read_vio.nbytes != INT64_MAX)) {
     send_event = VC_EVENT_READ_COMPLETE;
   }
 
@@ -612,11 +609,11 @@
 void
 Http2Stream::restart_sending()
 {
-  if (!this->response_header_done) {
+  if (!this->parsing_header_done) {
     return;
   }
 
-  IOBufferReader *reader = this->response_get_data_reader();
+  IOBufferReader *reader = this->get_data_reader_for_send();
   if (reader && !reader->is_read_avail_more_than(0)) {
     return;
   }
@@ -625,13 +622,13 @@
     return;
   }
 
-  this->send_response_body(true);
+  this->send_body(true);
 }
 
 void
 Http2Stream::update_write_request(bool call_update)
 {
-  if (!this->is_client_state_writeable() || closed || _proxy_ssn == nullptr || write_vio.mutex == nullptr ||
+  if (!this->is_state_writeable() || closed || _proxy_ssn == nullptr || write_vio.mutex == nullptr ||
       write_vio.get_reader() == nullptr) {
     return;
   }
@@ -652,19 +649,19 @@
   }
 
   // Process the new data
-  if (!this->response_header_done) {
+  if (!this->parsing_header_done) {
     // Still parsing the response_header
     int bytes_used = 0;
-    int state      = this->response_header.parse_resp(&http_parser, vio_reader, &bytes_used, false);
+    int state      = this->_send_header.parse_resp(&http_parser, vio_reader, &bytes_used, false);
     // HTTPHdr::parse_resp() consumed the vio_reader in above (consumed size is `bytes_used`)
     write_vio.ndone += bytes_used;
 
     switch (state) {
     case PARSE_RESULT_DONE: {
-      this->response_header_done = true;
+      this->parsing_header_done = true;
 
       // Schedule session shutdown if response header has "Connection: close"
-      MIMEField *field = this->response_header.field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION);
+      MIMEField *field = this->_send_header.field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION);
       if (field) {
         int len;
         const char *value = field->value_get(&len);
@@ -683,10 +680,10 @@
       }
 
       // Roll back states of response header to read final response
-      if (this->response_header.expect_final_response()) {
-        this->response_header_done = false;
-        response_header.destroy();
-        response_header.create(HTTP_TYPE_RESPONSE, HTTP_2_0);
+      if (this->_send_header.expect_final_response()) {
+        this->parsing_header_done = false;
+        _send_header.destroy();
+        _send_header.create(HTTP_TYPE_RESPONSE, HTTP_2_0);
         http_parser_clear(&http_parser);
         http_parser_init(&http_parser);
       }
@@ -695,7 +692,7 @@
 
       if (vio_reader->is_read_avail_more_than(0)) {
         this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES);
-        this->send_response_body(call_update);
+        this->send_body(call_update);
       }
       break;
     }
@@ -707,7 +704,7 @@
     }
   } else {
     this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES);
-    this->send_response_body(call_update);
+    this->send_body(call_update);
   }
 
   return;
@@ -786,7 +783,7 @@
 }
 
 void
-Http2Stream::send_response_body(bool call_update)
+Http2Stream::send_body(bool call_update)
 {
   Http2ClientSession *h2_proxy_ssn = static_cast<Http2ClientSession *>(this->_proxy_ssn);
   _timeout.update_inactivity();
@@ -826,7 +823,7 @@
 }
 
 IOBufferReader *
-Http2Stream::response_get_data_reader() const
+Http2Stream::get_data_reader_for_send() const
 {
   return write_vio.get_reader();
 }
@@ -917,15 +914,15 @@
 }
 
 ssize_t
-Http2Stream::client_rwnd() const
+Http2Stream::get_peer_rwnd() const
 {
-  return this->_client_rwnd;
+  return this->_peer_rwnd;
 }
 
 Http2ErrorCode
-Http2Stream::increment_client_rwnd(size_t amount)
+Http2Stream::increment_peer_rwnd(size_t amount)
 {
-  this->_client_rwnd += amount;
+  this->_peer_rwnd += amount;
 
   this->_recent_rwnd_increment[this->_recent_rwnd_increment_index] = amount;
   ++this->_recent_rwnd_increment_index;
@@ -939,10 +936,10 @@
 }
 
 Http2ErrorCode
-Http2Stream::decrement_client_rwnd(size_t amount)
+Http2Stream::decrement_peer_rwnd(size_t amount)
 {
-  this->_client_rwnd -= amount;
-  if (this->_client_rwnd < 0) {
+  this->_peer_rwnd -= amount;
+  if (this->_peer_rwnd < 0) {
     return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
   } else {
     return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
@@ -950,23 +947,23 @@
 }
 
 ssize_t
-Http2Stream::server_rwnd() const
+Http2Stream::get_local_rwnd() const
 {
-  return this->_server_rwnd;
+  return this->_local_rwnd;
 }
 
 Http2ErrorCode
-Http2Stream::increment_server_rwnd(size_t amount)
+Http2Stream::increment_local_rwnd(size_t amount)
 {
-  this->_server_rwnd += amount;
+  this->_local_rwnd += amount;
   return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
 }
 
 Http2ErrorCode
-Http2Stream::decrement_server_rwnd(size_t amount)
+Http2Stream::decrement_local_rwnd(size_t amount)
 {
-  this->_server_rwnd -= amount;
-  if (this->_server_rwnd < 0) {
+  this->_local_rwnd -= amount;
+  if (this->_local_rwnd < 0) {
     return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
   } else {
     return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h
index fe00269..01c5b88 100644
--- a/proxy/http2/Http2Stream.h
+++ b/proxy/http2/Http2Stream.h
@@ -55,7 +55,7 @@
   using super           = ProxyTransaction; ///< Parent type.
 
   Http2Stream() {} // Just to satisfy ClassAllocator
-  Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_rwnd);
+  Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_peer_rwnd, ssize_t initial_local_rwnd);
   ~Http2Stream();
 
   int main_event_handler(int event, void *edata);
@@ -78,18 +78,28 @@
 
   void signal_read_event(int event);
   void signal_write_event(int event);
-  void signal_write_event(bool call_update);
+  static constexpr auto CALL_UPDATE = true;
+  void signal_write_event(bool call_update = CALL_UPDATE);
 
   void restart_sending();
   bool push_promise(URL &url, const MIMEField *accept_encoding);
 
   // Stream level window size
-  ssize_t client_rwnd() const;
-  Http2ErrorCode increment_client_rwnd(size_t amount);
-  Http2ErrorCode decrement_client_rwnd(size_t amount);
-  ssize_t server_rwnd() const;
-  Http2ErrorCode increment_server_rwnd(size_t amount);
-  Http2ErrorCode decrement_server_rwnd(size_t amount);
+  // The following peer versions are our accounting of how many bytes we can
+  // send to the peer in order to respect their advertised receive window.
+  ssize_t get_peer_rwnd() const;
+  Http2ErrorCode increment_peer_rwnd(size_t amount);
+  Http2ErrorCode decrement_peer_rwnd(size_t amount);
+
+  // The following local versions are the accounting of how big our receive
+  // window is that we have communicated to the peer and which the peer needs
+  // to respect when sending us data. We use this for calculating whether the
+  // peer has exceeded the window size by sending us too many bytes and we also
+  // use this to calculate WINDOW_UPDATE frame increment values to send to the
+  // peer.
+  ssize_t get_local_rwnd() const;
+  Http2ErrorCode increment_local_rwnd(size_t amount);
+  Http2ErrorCode decrement_local_rwnd(size_t amount);
 
   /////////////////
   // Accessors
@@ -109,9 +119,9 @@
 
   void clear_io_events();
 
-  bool is_client_state_writeable() const;
+  bool is_state_writeable() const;
   bool is_closed() const;
-  IOBufferReader *response_get_data_reader() const;
+  IOBufferReader *get_data_reader_for_send() const;
 
   bool has_request_body(int64_t content_length, bool is_chunked_set) const override;
 
@@ -124,9 +134,10 @@
   Http2StreamId get_id() const;
   Http2StreamState get_state() const;
   bool change_state(uint8_t type, uint8_t flags);
-  void update_initial_rwnd(Http2WindowSize new_size);
+  void set_peer_rwnd(Http2WindowSize new_size);
+  void set_local_rwnd(Http2WindowSize new_size);
   bool has_trailing_header() const;
-  void set_request_headers(HTTPHdr &h2_headers);
+  void set_receive_headers(HTTPHdr &h2_headers);
   MIOBuffer *read_vio_writer() const;
   int64_t read_vio_read_avail();
 
@@ -135,19 +146,19 @@
   uint8_t *header_blocks        = nullptr;
   uint32_t header_blocks_length = 0; // total length of header blocks (not include Padding or other fields)
 
-  bool recv_end_stream = false;
-  bool send_end_stream = false;
+  bool receive_end_stream = false;
+  bool send_end_stream    = false;
 
-  bool response_header_done      = false;
+  bool parsing_header_done       = false;
   bool is_first_transaction_flag = false;
 
-  HTTPHdr response_header;
+  HTTPHdr _send_header;
   Http2DependencyTree::Node *priority_node = nullptr;
 
 private:
   bool response_is_data_available() const;
   Event *send_tracked_event(Event *event, int send_event, VIO *vio);
-  void send_response_body(bool call_update);
+  void send_body(bool call_update);
   void _clear_timers();
 
   /**
@@ -164,8 +175,8 @@
   Http2StreamState _state = Http2StreamState::HTTP2_STREAM_STATE_IDLE;
   int64_t _http_sm_id     = -1;
 
-  HTTPHdr _req_header;
-  MIOBuffer _request_buffer = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
+  HTTPHdr _receive_header;
+  MIOBuffer _receive_buffer = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
   int64_t read_vio_nbytes;
   VIO read_vio;
   VIO write_vio;
@@ -173,8 +184,8 @@
   History<HISTORY_DEFAULT_SIZE> _history;
   Milestones<Http2StreamMilestone, static_cast<size_t>(Http2StreamMilestone::LAST_ENTRY)> _milestones;
 
-  bool trailing_header = false;
-  bool has_body        = false;
+  bool is_trailing_header = false;
+  bool has_body           = false;
 
   // A brief discussion of similar flags and state variables:  _state, closed, terminate_stream
   //
@@ -202,8 +213,8 @@
   uint64_t data_length = 0;
   uint64_t bytes_sent  = 0;
 
-  ssize_t _client_rwnd = 0;
-  ssize_t _server_rwnd = 0;
+  ssize_t _peer_rwnd  = 0;
+  ssize_t _local_rwnd = 0;
 
   std::array<size_t, 5> _recent_rwnd_increment = {SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX};
   int _recent_rwnd_increment_index             = 0;
@@ -258,21 +269,27 @@
 }
 
 inline void
-Http2Stream::update_initial_rwnd(Http2WindowSize new_size)
+Http2Stream::set_peer_rwnd(Http2WindowSize new_size)
 {
-  this->_client_rwnd = new_size;
+  this->_peer_rwnd = new_size;
+}
+
+inline void
+Http2Stream::set_local_rwnd(Http2WindowSize new_size)
+{
+  this->_local_rwnd = new_size;
 }
 
 inline bool
 Http2Stream::has_trailing_header() const
 {
-  return trailing_header;
+  return is_trailing_header;
 }
 
 inline void
-Http2Stream::set_request_headers(HTTPHdr &h2_headers)
+Http2Stream::set_receive_headers(HTTPHdr &h2_headers)
 {
-  _req_header.copy(&h2_headers);
+  _receive_header.copy(&h2_headers);
 }
 
 // Check entire DATA payload length if content-length: header is exist
@@ -285,12 +302,12 @@
 inline bool
 Http2Stream::payload_length_is_valid() const
 {
-  uint32_t content_length = _req_header.get_content_length();
+  uint32_t content_length = _receive_header.get_content_length();
   return content_length == 0 || content_length == data_length;
 }
 
 inline bool
-Http2Stream::is_client_state_writeable() const
+Http2Stream::is_state_writeable() const
 {
   return _state == Http2StreamState::HTTP2_STREAM_STATE_OPEN || _state == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE ||
          _state == Http2StreamState::HTTP2_STREAM_STATE_RESERVED_LOCAL;
diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am
index faf6ab6..c0444d0 100644
--- a/proxy/http2/Makefile.am
+++ b/proxy/http2/Makefile.am
@@ -29,6 +29,7 @@
 	-I$(abs_top_srcdir)/proxy/hdrs \
 	-I$(abs_top_srcdir)/proxy/shared \
 	-I$(abs_top_srcdir)/proxy/http/remap \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES)
 
 noinst_LIBRARIES = libhttp2.a
@@ -78,7 +79,7 @@
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/mgmt/libmgmt_p.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@
+	@SWOC_LIBS@ @HWLOC_LIBS@
 
 if OS_LINUX
 test_libhttp2_LDFLAGS = $(AM_LDFLAGS)\
@@ -99,7 +100,8 @@
 
 test_Http2DependencyTree_LDADD = \
 	$(top_builddir)/src/tscore/libtscore.la \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	@SWOC_LIBS@
 
 test_Http2DependencyTree_CPPFLAGS = $(AM_CPPFLAGS)\
 	-I$(abs_top_srcdir)/tests/include
@@ -111,7 +113,8 @@
 test_Http2FrequencyCounter_LDADD = \
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
 	$(top_builddir)/src/tscore/libtscore.la \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	@SWOC_LIBS@
 
 test_Http2FrequencyCounter_CPPFLAGS = $(AM_CPPFLAGS)\
 	-I$(abs_top_srcdir)/tests/include
@@ -129,7 +132,7 @@
 	$(top_builddir)/src/records/librecords_p.a \
 	$(top_builddir)/mgmt/libmgmt_p.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@
+	@SWOC_LIBS@ @HWLOC_LIBS@
 
 test_HPACK_SOURCES = \
 	test_HPACK.cc \
diff --git a/proxy/http3/Http3App.cc b/proxy/http3/Http3App.cc
index 1de466a..773bad2 100644
--- a/proxy/http3/Http3App.cc
+++ b/proxy/http3/Http3App.cc
@@ -137,13 +137,19 @@
     }
     break;
   case VC_EVENT_WRITE_READY:
-  case VC_EVENT_WRITE_COMPLETE:
     if (is_bidirectional) {
       this->_handle_bidi_stream_on_write_ready(event, vio);
     } else {
       this->_handle_uni_stream_on_write_ready(event, vio);
     }
     break;
+  case VC_EVENT_WRITE_COMPLETE:
+    if (is_bidirectional) {
+      this->_handle_bidi_stream_on_write_complete(event, vio);
+    } else {
+      this->_handle_uni_stream_on_write_complete(event, vio);
+    }
+    break;
   case VC_EVENT_EOS:
     if (is_bidirectional) {
       this->_handle_bidi_stream_on_eos(event, vio);
@@ -282,6 +288,12 @@
 }
 
 void
+Http3App::_handle_uni_stream_on_write_complete(int /* event */, VIO *vio)
+{
+  // QUICStreamVCAdapter *adapter = static_cast<QUICStreamVCAdapter *>(vio->vc_server);
+}
+
+void
 Http3App::_handle_bidi_stream_on_eos(int /* event */, VIO *vio)
 {
   // TODO: handle eos
@@ -327,6 +339,23 @@
   }
 }
 
+void
+Http3App::_handle_bidi_stream_on_write_complete(int event, VIO *vio)
+{
+  QUICStreamVCAdapter *adapter = static_cast<QUICStreamVCAdapter *>(vio->vc_server);
+
+  QUICStreamId stream_id = adapter->stream().id();
+  Http3Transaction *txn  = static_cast<Http3Transaction *>(this->_ssn->get_transaction(stream_id));
+  if (txn != nullptr) {
+    SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread());
+    txn->handleEvent(event);
+  }
+  // FIXME There may be data to read
+  this->_ssn->remove_transaction(txn);
+  this->_qc->stream_manager()->delete_stream(stream_id);
+  this->_streams.erase(stream_id);
+}
+
 //
 // SETTINGS frame handler
 //
@@ -396,7 +425,7 @@
 
   this->_is_sent = true;
 
-  Http3Config::scoped_config params;
+  ts::Http3Config::scoped_config params;
 
   Http3SettingsFrame *frame = http3SettingsFrameAllocator.alloc();
   new (frame) Http3SettingsFrame();
diff --git a/proxy/http3/Http3App.h b/proxy/http3/Http3App.h
index 5de5aea..6cc8bee 100644
--- a/proxy/http3/Http3App.h
+++ b/proxy/http3/Http3App.h
@@ -68,9 +68,11 @@
 private:
   void _handle_uni_stream_on_read_ready(int event, VIO *vio);
   void _handle_uni_stream_on_write_ready(int event, VIO *vio);
+  void _handle_uni_stream_on_write_complete(int event, VIO *vio);
   void _handle_uni_stream_on_eos(int event, VIO *vio);
   void _handle_bidi_stream_on_read_ready(int event, VIO *vio);
   void _handle_bidi_stream_on_write_ready(int event, VIO *vio);
+  void _handle_bidi_stream_on_write_complete(int event, VIO *vio);
   void _handle_bidi_stream_on_eos(int event, VIO *vio);
 
   void _set_qpack_stream(Http3StreamType type, QUICStreamVCAdapter *adapter);
diff --git a/proxy/http3/Http3Config.cc b/proxy/http3/Http3Config.cc
index a152af5..055838b 100644
--- a/proxy/http3/Http3Config.cc
+++ b/proxy/http3/Http3Config.cc
@@ -23,13 +23,13 @@
 
 #include "Http3Config.h"
 
-int Http3Config::_config_id = 0;
+int ts::Http3Config::_config_id = 0;
 
 //
 // Http3ConfigParams
 //
 void
-Http3ConfigParams::initialize()
+ts::Http3ConfigParams::initialize()
 {
   REC_EstablishStaticConfigInt32U(this->_header_table_size, "proxy.config.http3.header_table_size");
   REC_EstablishStaticConfigInt32U(this->_max_header_list_size, "proxy.config.http3.max_header_list_size");
@@ -39,31 +39,31 @@
 }
 
 uint32_t
-Http3ConfigParams::header_table_size() const
+ts::Http3ConfigParams::header_table_size() const
 {
   return this->_header_table_size;
 }
 
 uint32_t
-Http3ConfigParams::max_header_list_size() const
+ts::Http3ConfigParams::max_header_list_size() const
 {
   return this->_max_header_list_size;
 }
 
 uint32_t
-Http3ConfigParams::qpack_blocked_streams() const
+ts::Http3ConfigParams::qpack_blocked_streams() const
 {
   return this->_qpack_blocked_streams;
 }
 
 uint32_t
-Http3ConfigParams::num_placeholders() const
+ts::Http3ConfigParams::num_placeholders() const
 {
   return this->_num_placeholders;
 }
 
 uint32_t
-Http3ConfigParams::max_settings() const
+ts::Http3ConfigParams::max_settings() const
 {
   return this->_max_settings;
 }
@@ -72,29 +72,29 @@
 // Http3Config
 //
 void
-Http3Config::startup()
+ts::Http3Config::startup()
 {
   reconfigure();
 }
 
 void
-Http3Config::reconfigure()
+ts::Http3Config::reconfigure()
 {
   Http3ConfigParams *params;
   params = new Http3ConfigParams;
   // re-read configuration
   params->initialize();
-  Http3Config::_config_id = configProcessor.set(Http3Config::_config_id, params);
+  ts::Http3Config::_config_id = configProcessor.set(ts::Http3Config::_config_id, params);
 }
 
-Http3ConfigParams *
-Http3Config::acquire()
+ts::Http3ConfigParams *
+ts::Http3Config::acquire()
 {
-  return static_cast<Http3ConfigParams *>(configProcessor.get(Http3Config::_config_id));
+  return static_cast<ts::Http3ConfigParams *>(configProcessor.get(ts::Http3Config::_config_id));
 }
 
 void
-Http3Config::release(Http3ConfigParams *params)
+ts::Http3Config::release(Http3ConfigParams *params)
 {
-  configProcessor.release(Http3Config::_config_id, params);
+  configProcessor.release(ts::Http3Config::_config_id, params);
 }
diff --git a/proxy/http3/Http3Config.h b/proxy/http3/Http3Config.h
index 27ae9a2..5efbbef 100644
--- a/proxy/http3/Http3Config.h
+++ b/proxy/http3/Http3Config.h
@@ -25,6 +25,8 @@
 
 #include "ProxyConfig.h"
 
+namespace ts
+{
 class Http3ConfigParams : public ConfigInfo
 {
 public:
@@ -60,3 +62,5 @@
 private:
   static int _config_id;
 };
+
+} // namespace ts
diff --git a/proxy/http3/Http3Frame.cc b/proxy/http3/Http3Frame.cc
index 6bb195e..3b7c611 100644
--- a/proxy/http3/Http3Frame.cc
+++ b/proxy/http3/Http3Frame.cc
@@ -396,7 +396,7 @@
 Http3FrameUPtr
 Http3FrameFactory::create(const uint8_t *buf, size_t len)
 {
-  Http3Config::scoped_config params;
+  ts::Http3Config::scoped_config params;
   Http3Frame *frame   = nullptr;
   Http3FrameType type = Http3Frame::type(buf, len);
 
diff --git a/proxy/http3/Http3Session.cc b/proxy/http3/Http3Session.cc
index b19d501..60d0f0f 100644
--- a/proxy/http3/Http3Session.cc
+++ b/proxy/http3/Http3Session.cc
@@ -51,6 +51,14 @@
   return;
 }
 
+void
+HQSession::remove_transaction(HQTransaction *trans)
+{
+  this->_transaction_list.remove(trans);
+
+  return;
+}
+
 const char *
 HQSession::get_protocol_string() const
 {
diff --git a/proxy/http3/Http3Session.h b/proxy/http3/Http3Session.h
index bfb37d6..4d78b27 100644
--- a/proxy/http3/Http3Session.h
+++ b/proxy/http3/Http3Session.h
@@ -53,7 +53,8 @@
   int get_transact_count() const override;
 
   // HQSession
-  void add_transaction(HQTransaction *);
+  void add_transaction(HQTransaction *trans);
+  void remove_transaction(HQTransaction *trans);
   HQTransaction *get_transaction(QUICStreamId);
 
 private:
diff --git a/proxy/http3/Http3SessionAccept.cc b/proxy/http3/Http3SessionAccept.cc
index 1639456..4ad8486 100644
--- a/proxy/http3/Http3SessionAccept.cc
+++ b/proxy/http3/Http3SessionAccept.cc
@@ -62,11 +62,11 @@
 
   std::string_view alpn = qvc->negotiated_application_name();
 
-  if (IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_QUIC_D27.compare(alpn) == 0) {
+  if (IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_QUIC_D29.compare(alpn) == 0) {
     Debug("http3", "[%s] start HTTP/0.9 app (ALPN=%.*s)", qvc->cids().data(), static_cast<int>(alpn.length()), alpn.data());
 
     new Http09App(qvc, std::move(session_acl), this->options);
-  } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_3_D27.compare(alpn) == 0) {
+  } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_3_D29.compare(alpn) == 0) {
     Debug("http3", "[%s] start HTTP/3 app (ALPN=%.*s)", qvc->cids().data(), static_cast<int>(alpn.length()), alpn.data());
 
     Http3App *app = new Http3App(qvc, std::move(session_acl), this->options);
diff --git a/proxy/http3/Http3Transaction.cc b/proxy/http3/Http3Transaction.cc
index b8ffca7..823cd3c 100644
--- a/proxy/http3/Http3Transaction.cc
+++ b/proxy/http3/Http3Transaction.cc
@@ -356,13 +356,13 @@
     this->_info.read_vio->reenable();
     break;
   case VC_EVENT_READ_COMPLETE:
+    Http3TransVDebug("%s (%d)", get_vc_event_name(event), event);
+    this->_process_read_vio();
     if (!this->_header_handler->is_complete()) {
       // Delay processing READ_COMPLETE
       this_ethread()->schedule_imm(this, VC_EVENT_READ_COMPLETE);
       break;
     }
-    Http3TransVDebug("%s (%d)", get_vc_event_name(event), event);
-    this->_process_read_vio();
     this->_data_handler->finalize();
     // always signal regardless of progress
     this->_signal_read_event();
@@ -450,7 +450,7 @@
 int64_t
 Http3Transaction::_process_read_vio()
 {
-  if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) {
+  if (this->_info.read_vio->cont == nullptr || this->_info.read_vio->op == VIO::NONE) {
     return 0;
   }
 
@@ -463,17 +463,18 @@
     return 0;
   }
 
-  SCOPED_MUTEX_LOCK(lock, this->_read_vio.mutex, this_ethread());
+  SCOPED_MUTEX_LOCK(lock, this->_info.read_vio->mutex, this_ethread());
 
   uint64_t nread = 0;
   this->_frame_dispatcher.on_read_ready(this->_info.adapter.stream().id(), *this->_info.read_vio->get_reader(), nread);
+  this->_info.read_vio->ndone += nread;
   return nread;
 }
 
 int64_t
 Http3Transaction::_process_write_vio()
 {
-  if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) {
+  if (this->_info.write_vio->cont == nullptr || this->_info.write_vio->op == VIO::NONE) {
     return 0;
   }
 
@@ -486,15 +487,15 @@
     return 0;
   }
 
-  SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread());
+  SCOPED_MUTEX_LOCK(lock, this->_info.write_vio->mutex, this_ethread());
 
   size_t nwritten = 0;
   bool all_done   = false;
   this->_frame_collector.on_write_ready(this->_info.adapter.stream().id(), *this->_info.write_vio->get_writer(), nwritten,
                                         all_done);
-  this->_info.write_vio->nbytes += nwritten;
+  this->_sent_bytes += nwritten;
   if (all_done) {
-    this->_info.write_vio->done();
+    this->_info.write_vio->nbytes = this->_sent_bytes;
   }
 
   return nwritten;
diff --git a/proxy/http3/Http3Transaction.h b/proxy/http3/Http3Transaction.h
index b1ddbb6..155d519 100644
--- a/proxy/http3/Http3Transaction.h
+++ b/proxy/http3/Http3Transaction.h
@@ -82,6 +82,8 @@
   MIOBuffer _read_vio_buf = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
   QUICStreamVCAdapter::IOInfo &_info;
 
+  size_t _sent_bytes = 0;
+
   VIO _read_vio;
   VIO _write_vio;
   Event *_read_event  = nullptr;
diff --git a/proxy/http3/Makefile.am b/proxy/http3/Makefile.am
index a83f2a7..98046b4 100644
--- a/proxy/http3/Makefile.am
+++ b/proxy/http3/Makefile.am
@@ -30,7 +30,7 @@
   -I$(abs_top_srcdir)/proxy/hdrs \
   -I$(abs_top_srcdir)/proxy/shared \
   -I$(abs_top_srcdir)/proxy/http/remap \
-  $(TS_INCLUDES) \
+  $(TS_INCLUDES) @SWOC_INCLUDES@ \
 	@YAMLCPP_INCLUDES@
 
 noinst_LIBRARIES = libhttp3.a
@@ -80,7 +80,7 @@
   $(top_builddir)/proxy/shared/libUglyLogStubs.a \
   @LIBPCRE@ \
   @OPENSSL_LIBS@ \
-  @HWLOC_LIBS@
+  @HWLOC_LIBS@ @SWOC_LIBS@
 
 test_libhttp3_CPPFLAGS = $(test_CPPFLAGS)
 test_libhttp3_LDFLAGS = @AM_LDFLAGS@
diff --git a/proxy/http3/test/main.cc b/proxy/http3/test/main.cc
index e9a7307..ef2b119 100644
--- a/proxy/http3/test/main.cc
+++ b/proxy/http3/test/main.cc
@@ -56,7 +56,7 @@
     Thread *main_thread = new EThread;
     main_thread->set_specific();
 
-    Http3Config::startup();
+    ts::Http3Config::startup();
   }
 };
 CATCH_REGISTER_LISTENER(EventProcessorListener);
diff --git a/proxy/http3/test/test_QPACK.cc b/proxy/http3/test/test_QPACK.cc
index 178a6e3..6de8e18 100644
--- a/proxy/http3/test/test_QPACK.cc
+++ b/proxy/http3/test/test_QPACK.cc
@@ -65,11 +65,19 @@
 };
 
 // TODO: QUICUnidirectionalStream should be used if there
+#if HAVE_QUICHE_H
+class TestQUICStream : public QUICStreamImpl
+#else
 class TestQUICStream : public QUICBidirectionalStream
+#endif
 {
 public:
   TestQUICStream(QUICStreamId sid)
+#if HAVE_QUICHE_H
+    : QUICStreamImpl(new MockQUICConnectionInfoProvider(), sid)
+#else
     : QUICBidirectionalStream(new MockQUICRTTProvider(), new MockQUICConnectionInfoProvider(), sid, 65536, 65536)
+#endif
   {
   }
 
diff --git a/proxy/logging/Log.cc b/proxy/logging/Log.cc
index c781950..7b37410 100644
--- a/proxy/logging/Log.cc
+++ b/proxy/logging/Log.cc
@@ -53,6 +53,8 @@
 
 #include "tscore/ink_apidefs.h"
 
+#include "MgmtDefs.h"
+
 #define PERIODIC_TASKS_INTERVAL_FALLBACK 5
 
 // Log global objects
@@ -401,11 +403,21 @@
   global_field_list.add(field, false);
   field_symbol_hash.emplace("cqu", field);
 
+  field = new LogField("client_req_url", "pqu", LogField::STRING, &LogAccess::marshal_client_req_url, &LogAccess::unmarshal_str,
+                       &LogAccess::set_client_req_url);
+  global_field_list.add(field, false);
+  field_symbol_hash.emplace("pqu", field);
+
   field = new LogField("client_req_url_canonical", "cquc", LogField::STRING, &LogAccess::marshal_client_req_url_canon,
                        &LogAccess::unmarshal_str, &LogAccess::set_client_req_url_canon);
   global_field_list.add(field, false);
   field_symbol_hash.emplace("cquc", field);
 
+  field = new LogField("client_req_url_canonical", "pquc", LogField::STRING, &LogAccess::marshal_client_req_url_canon,
+                       &LogAccess::unmarshal_str, &LogAccess::set_client_req_url_canon);
+  global_field_list.add(field, false);
+  field_symbol_hash.emplace("pquc", field);
+
   field =
     new LogField("client_req_unmapped_url_canonical", "cquuc", LogField::STRING, &LogAccess::marshal_client_req_unmapped_url_canon,
                  &LogAccess::unmarshal_str, &LogAccess::set_client_req_unmapped_url_canon);
@@ -427,15 +439,20 @@
   global_field_list.add(field, false);
   field_symbol_hash.emplace("cqus", field);
 
+  field = new LogField("client_req_url_scheme", "pqus", LogField::STRING, &LogAccess::marshal_client_req_url_scheme,
+                       &LogAccess::unmarshal_str);
+  global_field_list.add(field, false);
+  field_symbol_hash.emplace("pqus", field);
+
   field = new LogField("client_req_url_path", "cqup", LogField::STRING, &LogAccess::marshal_client_req_url_path,
                        &LogAccess::unmarshal_str, &LogAccess::set_client_req_url_path);
   global_field_list.add(field, false);
   field_symbol_hash.emplace("cqup", field);
 
-  field = new LogField("client_req_http_version", "cqhv", LogField::dINT, &LogAccess::marshal_client_req_http_version,
-                       &LogAccess::unmarshal_http_version);
+  field = new LogField("client_req_url_path", "pqup", LogField::STRING, &LogAccess::marshal_client_req_url_path,
+                       &LogAccess::unmarshal_str, &LogAccess::set_client_req_url_path);
   global_field_list.add(field, false);
-  field_symbol_hash.emplace("cqhv", field);
+  field_symbol_hash.emplace("pqup", field);
 
   field = new LogField("client_req_protocol_version", "cqpv", LogField::dINT, &LogAccess::marshal_client_req_protocol_version,
                        &LogAccess::unmarshal_str);
@@ -1081,8 +1098,6 @@
       LogConfig::register_config_callbacks();
     }
 
-    LogConfig::register_mgmt_callbacks();
-
     // create the flush thread
     create_threads();
     eventProcessor.schedule_every(new PeriodicWakeup(preproc_threads, 1), HRTIME_SECOND, ET_CALL);
diff --git a/proxy/logging/LogAccess.cc b/proxy/logging/LogAccess.cc
index a726a35..7a6eaad 100644
--- a/proxy/logging/LogAccess.cc
+++ b/proxy/logging/LogAccess.cc
@@ -916,8 +916,8 @@
 /*-------------------------------------------------------------------------
   LogAccess::unmarshal_http_text
 
-  The http text is simply the fields http_method (cqhm) + url (cqu) +
-  http_version (cqhv), all right next to each other, in that order.
+  The http text is a reproduced HTTP/1.x request line. It's HTTP method (cqhm) + URL (pqu) + HTTP version.
+  This doesn't support HTTP/2 and HTTP/3 since those don't have a request line.
   -------------------------------------------------------------------------*/
 
 int
diff --git a/proxy/logging/LogBuffer.h b/proxy/logging/LogBuffer.h
index dafccec..bd8d32f 100644
--- a/proxy/logging/LogBuffer.h
+++ b/proxy/logging/LogBuffer.h
@@ -24,6 +24,7 @@
 #pragma once
 
 #include "tscore/ink_platform.h"
+#include "tscore/ink_atomic.h"
 #include "tscore/Diags.h"
 #include "LogFormat.h"
 #include "LogLimits.h"
diff --git a/proxy/logging/LogConfig.cc b/proxy/logging/LogConfig.cc
index ecf46c4..b840295 100644
--- a/proxy/logging/LogConfig.cc
+++ b/proxy/logging/LogConfig.cc
@@ -99,7 +99,7 @@
   logbuffer_max_iobuf_index = BUFFER_SIZE_INDEX_32K;
 }
 
-void LogConfig::reconfigure_mgmt_variables(ts::MemSpan<void>)
+void LogConfig::reconfigure_mgmt_variables(swoc::MemSpan<void>)
 {
   Note("received log reconfiguration event, rolling now");
   Log::config->roll_log_files_now = true;
@@ -565,19 +565,6 @@
 }
 
 /*-------------------------------------------------------------------------
-  LogConfig::register_mgmt_callbacks
-
-  This static function is called by Log::init to register the mgmt callback
-  function for each of the logging mgmt messages.
-  -------------------------------------------------------------------------*/
-
-void
-LogConfig::register_mgmt_callbacks()
-{
-  RecRegisterManagerCb(REC_EVENT_ROLL_LOG_FILES, &LogConfig::reconfigure_mgmt_variables);
-}
-
-/*-------------------------------------------------------------------------
   LogConfig::space_to_write
 
   This function returns true if there is enough disk space to write the
@@ -642,7 +629,6 @@
   if (!logfile_dir) {
     const char *msg = "Logging directory not specified";
     Error("%s", msg);
-    LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, "%s", msg);
     m_log_directory_inaccessible = true;
     return;
   }
@@ -656,7 +642,6 @@
   if (err < 0) {
     const char *msg = "Error accessing logging directory %s: %s.";
     Error(msg, logfile_dir, strerror(errno));
-    LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, logfile_dir, strerror(errno));
     m_log_directory_inaccessible = true;
     return;
   }
@@ -665,7 +650,6 @@
   if (ld == nullptr) {
     const char *msg = "Error opening logging directory %s to perform a space check: %s.";
     Error(msg, logfile_dir, strerror(errno));
-    LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, logfile_dir, strerror(errno));
     m_log_directory_inaccessible = true;
     return;
   }
@@ -788,7 +772,6 @@
     if (m_space_used >= max_space) {
       if (!m_disk_full) {
         m_disk_full = true;
-        LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, DISK_IS_CONFIG_FULL_MESSAGE);
         Warning(DISK_IS_CONFIG_FULL_MESSAGE);
       }
     }
@@ -798,7 +781,6 @@
     else if (m_partition_space_left <= 0) {
       if (!m_partition_full) {
         m_partition_full = true;
-        LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, DISK_IS_ACTUAL_FULL_MESSAGE);
         Warning(DISK_IS_ACTUAL_FULL_MESSAGE);
       }
     }
@@ -808,13 +790,11 @@
     else if (m_space_used + headroom >= max_space) {
       if (!m_disk_low) {
         m_disk_low = true;
-        LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, DISK_IS_CONFIG_LOW_MESSAGE);
         Warning(DISK_IS_CONFIG_LOW_MESSAGE);
       }
     } else {
       if (!m_partition_low) {
         m_partition_low = true;
-        LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, DISK_IS_ACTUAL_LOW_MESSAGE);
         Warning(DISK_IS_ACTUAL_LOW_MESSAGE);
       }
     }
diff --git a/proxy/logging/LogConfig.h b/proxy/logging/LogConfig.h
index 9cbcefc..149c774 100644
--- a/proxy/logging/LogConfig.h
+++ b/proxy/logging/LogConfig.h
@@ -31,7 +31,7 @@
 #include "ProxyConfig.h"
 #include "LogObject.h"
 #include "RolledLogDeleter.h"
-#include "tscpp/util/MemSpan.h"
+#include "swoc/MemSpan.h"
 
 /* Instead of enumerating the stats in DynamicStats.h, each module needs
    to enumerate its stats separately and register them with librecords
@@ -116,7 +116,6 @@
 
   static void register_config_callbacks();
   static void register_stat_callbacks();
-  static void register_mgmt_callbacks();
 
   bool space_to_write(int64_t bytes_to_write) const;
 
@@ -137,7 +136,7 @@
   void read_configuration_variables();
 
   // CVR This is the mgmt callback function, hence all the strange arguments
-  static void reconfigure_mgmt_variables(ts::MemSpan<void>);
+  static void reconfigure_mgmt_variables(swoc::MemSpan<void>);
 
   int
   get_max_space_mb() const
diff --git a/proxy/logging/LogFile.cc b/proxy/logging/LogFile.cc
index c575a08..1f73033 100644
--- a/proxy/logging/LogFile.cc
+++ b/proxy/logging/LogFile.cc
@@ -93,27 +93,6 @@
   This (copy) constructor builds a LogFile object from another LogFile object.
   -------------------------------------------------------------------------*/
 
-LogFile::LogFile(const LogFile &copy)
-  : RefCountObj(copy),
-    m_file_format(copy.m_file_format),
-    m_name(ats_strdup(copy.m_name)),
-    m_header(ats_strdup(copy.m_header)),
-    m_signature(copy.m_signature),
-    m_ascii_buffer_size(copy.m_ascii_buffer_size),
-    m_max_line_size(copy.m_max_line_size),
-    m_pipe_buffer_size(copy.m_pipe_buffer_size),
-    m_fd(copy.m_fd)
-{
-  ink_release_assert(m_ascii_buffer_size >= m_max_line_size);
-
-  if (copy.m_log) {
-    m_log = new BaseLogFile(*(copy.m_log));
-  } else {
-    m_log = nullptr;
-  }
-
-  Debug("log-file", "exiting LogFile copy constructor, m_name=%s, this=%p", m_name, this);
-}
 /*-------------------------------------------------------------------------
   LogFile::~LogFile
   -------------------------------------------------------------------------*/
@@ -763,7 +742,6 @@
   // XXX if open_file() returns, LOG_FILE_FILESYSTEM_CHECKS_FAILED, raise a more informative alarm ...
   if (err != LOG_FILE_NO_ERROR && err != LOG_FILE_NO_PIPE_READERS) {
     if (!failure_last_call) {
-      LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, "Traffic Server could not open logfile %s.", m_name);
       Warning("Traffic Server could not open logfile %s: %s.", m_name, strerror(errno));
     }
     failure_last_call = true;
diff --git a/proxy/logging/LogFile.h b/proxy/logging/LogFile.h
index 2d603d5..f192da1 100644
--- a/proxy/logging/LogFile.h
+++ b/proxy/logging/LogFile.h
@@ -44,7 +44,7 @@
 public:
   LogFile(const char *name, const char *header, LogFileFormat format, uint64_t signature, size_t ascii_buffer_size = 4 * 9216,
           size_t max_line_size = 9216, int pipe_buffer_size = 0, LogEscapeType escape_type = LOG_ESCAPE_NONE);
-  LogFile(const LogFile &);
+  LogFile(const LogFile &) = delete;
   ~LogFile() override;
 
   enum {
diff --git a/proxy/logging/LogFormat.cc b/proxy/logging/LogFormat.cc
index c3e8f81..4d62630 100644
--- a/proxy/logging/LogFormat.cc
+++ b/proxy/logging/LogFormat.cc
@@ -206,7 +206,7 @@
   -------------------------------------------------------------------------*/
 
 LogFormat::LogFormat(const LogFormat &rhs)
-  : RefCountObj(rhs),
+  : RefCountObj(),
     m_interval_sec(0),
     m_interval_next(0),
     m_agg_marshal_space(nullptr),
diff --git a/proxy/logging/LogObject.cc b/proxy/logging/LogObject.cc
index d2eafc6..670f7d0 100644
--- a/proxy/logging/LogObject.cc
+++ b/proxy/logging/LogObject.cc
@@ -987,7 +987,6 @@
       const char *se  = strerror(errno);
 
       Error(msg, filename, se);
-      LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename, se);
       retVal = ERROR_ACCESSING_LOG_FILE;
     }
   } else {
@@ -1020,7 +1019,6 @@
         const char *msg = "Cannot solve filename conflicts for log file %s";
 
         Error(msg, filename);
-        LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename);
         retVal = CANNOT_SOLVE_FILENAME_CONFLICTS;
       } else {
         // Either the meta file could not be read, or the new object's
@@ -1041,7 +1039,6 @@
             char *se        = strerror(errno);
 
             Error(msg, filename, se);
-            LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename, se);
             retVal    = ERROR_DETERMINING_FILE_INFO;
             roll_file = false;
           } else {
@@ -1083,7 +1080,6 @@
                     "conflicts (filename or log format): %s";
   const char *err = strerror(errno);
   Error(msg, filename, err);
-  LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename, err);
 }
 
 bool
@@ -1121,7 +1117,6 @@
       const char *msg = "Cannot solve filename conflicts for log file %s";
 
       Error(msg, filename);
-      LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename);
       retVal = CANNOT_SOLVE_FILENAME_CONFLICTS;
     }
   }
diff --git a/proxy/logging/LogStandalone.cc b/proxy/logging/LogStandalone.cc
index f147cb5..b5369dd 100644
--- a/proxy/logging/LogStandalone.cc
+++ b/proxy/logging/LogStandalone.cc
@@ -36,7 +36,6 @@
 #include "P_EventSystem.h"
 #include "records/P_RecProcess.h"
 
-#include "ProcessManager.h"
 #include "MgmtUtils.h"
 // Needs LibRecordsConfigInit()
 #include "RecordsConfig.h"
@@ -97,25 +96,13 @@
 {
   mgmt_use_syslog();
 
-  // Temporary Hack to Enable Communication with LocalManager
-  if (getenv("PROXY_REMOTE_MGMT")) {
-    remote_management_flag = true;
-  }
-
   // diags should have been initialized by caller, e.g.: sac.cc
   ink_assert(diags());
 
-  RecProcessInit(remote_management_flag ? RECM_CLIENT : RECM_STAND_ALONE, diags());
+  RecProcessInit(RECM_STAND_ALONE, diags());
   LibRecordsConfigInit();
 
-  // Start up manager
-  pmgmt = new ProcessManager(remote_management_flag);
-
-  pmgmt->start();
-
-  RecProcessInitMessage(remote_management_flag ? RECM_CLIENT : RECM_STAND_ALONE);
-
-  pmgmt->reconfigure();
+  RecProcessInitMessage(RECM_STAND_ALONE);
 
   //
   // Define version info records
diff --git a/proxy/logging/LogUtils.cc b/proxy/logging/LogUtils.cc
index ee64f32..3364f7a 100644
--- a/proxy/logging/LogUtils.cc
+++ b/proxy/logging/LogUtils.cc
@@ -57,8 +57,6 @@
 #include <netdb.h>
 
 #include "records/P_RecProcess.h"
-// REC_SIGNAL_LOGGING_ERROR    is defined in I_RecSignals.h
-// REC_SIGNAL_LOGGING_WARNING  is defined in I_RecSignals.h
 
 #include "LogUtils.h"
 #include "LogLimits.h"
@@ -209,46 +207,6 @@
 }
 
 /*-------------------------------------------------------------------------
-  LogUtils::manager_alarm
-
-  This routine provides a convenient abstraction for sending the traffic
-  server manager process an alarm.  The logging system can send
-  LOG_ALARM_N_TYPES different types of alarms, as defined in LogUtils.h.
-  Subsequent alarms of the same type will override the previous alarm
-  entry.
-  -------------------------------------------------------------------------*/
-
-void
-LogUtils::manager_alarm(LogUtils::AlarmType alarm_type, const char *msg, ...)
-{
-  char msg_buf[LOG_MAX_FORMATTED_LINE];
-
-  ink_assert(alarm_type >= 0 && alarm_type < LogUtils::LOG_ALARM_N_TYPES);
-
-  if (msg == nullptr) {
-    snprintf(msg_buf, sizeof(msg_buf), "No Message");
-  } else {
-    va_list ap;
-    va_start(ap, msg);
-    vsnprintf(msg_buf, LOG_MAX_FORMATTED_LINE, msg, ap);
-    va_end(ap);
-  }
-
-  switch (alarm_type) {
-  case LogUtils::LOG_ALARM_ERROR:
-    RecSignalManager(REC_SIGNAL_LOGGING_ERROR, msg_buf);
-    break;
-
-  case LogUtils::LOG_ALARM_WARNING:
-    RecSignalManager(REC_SIGNAL_LOGGING_WARNING, msg_buf);
-    break;
-
-  default:
-    ink_assert(false);
-  }
-}
-
-/*-------------------------------------------------------------------------
   LogUtils::strip_trailing_newline
 
   This routine examines the given string buffer to see if the last
diff --git a/proxy/logging/LogUtils.h b/proxy/logging/LogUtils.h
index ade7418..ffd3de9 100644
--- a/proxy/logging/LogUtils.h
+++ b/proxy/logging/LogUtils.h
@@ -49,7 +49,6 @@
 char *timestamp_to_date_str(long timestamp);
 char *timestamp_to_time_str(long timestamp);
 unsigned ip_from_host(char *host);
-void manager_alarm(AlarmType alarm_type, const char *msg, ...) TS_PRINTFLIKE(2, 3);
 void strip_trailing_newline(char *buf);
 char *escapify_url(Arena *arena, char *url, size_t len_in, int *len_out, char *dst = nullptr, size_t dst_size = 0,
                    const unsigned char *map = nullptr);
diff --git a/proxy/logging/unit-tests/test_LogUtils.cc b/proxy/logging/unit-tests/test_LogUtils.cc
index 341d58a..a38be6d 100644
--- a/proxy/logging/unit-tests/test_LogUtils.cc
+++ b/proxy/logging/unit-tests/test_LogUtils.cc
@@ -125,12 +125,6 @@
   std::exit(1);
 }
 
-void
-RecSignalManager(int, char const *, std::size_t)
-{
-  ink_release_assert(false);
-}
-
 TEST_CASE("get_unrolled_filename parses possible log files as expected", "[get_unrolled_filename]")
 {
   // Rolled log inputs.
diff --git a/proxy/logging/unit-tests/test_LogUtils.h b/proxy/logging/unit-tests/test_LogUtils.h
index 952662d..d5ad02e 100644
--- a/proxy/logging/unit-tests/test_LogUtils.h
+++ b/proxy/logging/unit-tests/test_LogUtils.h
@@ -24,7 +24,7 @@
 #pragma once
 
 #include <cstring>
-#include <tscpp/util/MemSpan.h>
+#include <swoc/MemSpan.h>
 
 struct MIMEField {
   const char *tag, *value;
@@ -46,7 +46,7 @@
 class MIMEHdr
 {
 private:
-  ts::MemSpan<MIMEField const> _fields;
+  swoc::MemSpan<MIMEField const> _fields;
 
 public:
   MIMEHdr(const MIMEField *first, int count) : _fields{first, size_t(count)} {}
diff --git a/proxy/logging/unit-tests/test_RolledLogDeleter.cc b/proxy/logging/unit-tests/test_RolledLogDeleter.cc
index d0e51cd..83f96ba 100644
--- a/proxy/logging/unit-tests/test_RolledLogDeleter.cc
+++ b/proxy/logging/unit-tests/test_RolledLogDeleter.cc
@@ -307,13 +307,4 @@
 
     verify_there_are_no_candidates(deleter);
   }
-}
-
-//
-// Stub
-//
-void
-RecSignalManager(int, const char *, unsigned long)
-{
-  ink_release_assert(false);
-}
+}
\ No newline at end of file
diff --git a/proxy/shared/Makefile.am b/proxy/shared/Makefile.am
index 1d54d40..2e5d7f4 100644
--- a/proxy/shared/Makefile.am
+++ b/proxy/shared/Makefile.am
@@ -35,6 +35,7 @@
 	-I$(abs_top_srcdir)/proxy/http \
 	-I$(abs_top_srcdir)/proxy/hdrs \
 	-I$(abs_top_srcdir)/proxy/logging \
+	@SWOC_INCLUDES@ \
 	$(TS_INCLUDES)
 
 libdiagsconfig_a_SOURCES = \
diff --git a/proxy/shared/UglyLogStubs.cc b/proxy/shared/UglyLogStubs.cc
index f845633..16e46cd 100644
--- a/proxy/shared/UglyLogStubs.cc
+++ b/proxy/shared/UglyLogStubs.cc
@@ -39,6 +39,12 @@
 
 class FakeUDPNetProcessor : public UDPNetProcessor
 {
+  EventType
+  register_event_type() override
+  {
+    return 999;
+  }
+
   int
   start(int, size_t) override
   {
diff --git a/rc/trafficserver.conf.in b/rc/trafficserver.conf.in
index 6d2aef2..ed64776 100644
--- a/rc/trafficserver.conf.in
+++ b/rc/trafficserver.conf.in
@@ -37,4 +37,4 @@
 	fi
 end script
 
-exec @exp_bindir@/traffic_manager
+exec @exp_bindir@/traffic_server
diff --git a/rc/trafficserver.in b/rc/trafficserver.in
index dc0093b..09d037e 100644
--- a/rc/trafficserver.in
+++ b/rc/trafficserver.in
@@ -109,15 +109,11 @@
 # For standard installations TS_BASE will be empty
 eval TS_BASE="`echo $TS_ROOT | ${ESED} -e 's;@prefix@$;;'`"
 
-TM_NAME=${TM_NAME:-traffic_manager}
 TS_NAME=${TS_NAME:-traffic_server}
-TM_DAEMON=${TM_DAEMON:-$TS_BASE@exp_bindir@/traffic_manager}
-TM_DAEMON_ARGS=""
 TS_DAEMON=${TS_DAEMON:-$TS_BASE@exp_bindir@/traffic_server}
 TS_DAEMON_ARGS=""
-TL_BINARY=${TL_BINARY:-$TS_BASE@exp_bindir@/traffic_ctl}
-TY_BINARY=${TL_BINARY:-$TS_BASE@exp_bindir@/traffic_layout}
-TM_PIDFILE=${TM_PIDFILE:-$TS_BASE@exp_runtimedir@/manager.lock}
+TC_BINARY=${TC_BINARY:-$TS_BASE@exp_bindir@/traffic_ctl}
+TY_BINARY=${TC_BINARY:-$TS_BASE@exp_bindir@/traffic_layout}
 TS_PIDFILE=${TS_PIDFILE:-$TS_BASE@exp_runtimedir@/server.lock}
 # number of times to retry check on pid lock file
 PIDFILE_CHECK_RETRIES=${PIDFILE_CHECK_RETRIES:-30}
@@ -206,8 +202,8 @@
     while (( $i < $PIDFILE_CHECK_RETRIES ))
     do
         # check for regular file and size greater than 0
-        if [[ -f $TM_PIDFILE ]] && [[ -s $TM_PIDFILE ]]; then
-            success || true
+        if [[ -f $TS_PIDFILE ]] && [[ -s $TS_PIDFILE ]]; then
+            success
             return 0
         fi
 
@@ -230,16 +226,16 @@
   #   0 if daemon has been started
   #   1 if daemon was already running
   #   2 if daemon could not be started
-    start-stop-daemon --start --quiet --pidfile $TM_PIDFILE --exec $TM_DAEMON --test > /dev/null \
+    start-stop-daemon --start --quiet --pidfile $TS_PIDFILE --exec $TS_DAEMON --test > /dev/null \
         || return 1
-    start-stop-daemon --start --background --quiet --pidfile $TM_PIDFILE --exec $TM_DAEMON -- \
-        $TM_DAEMON_ARGS \
+    start-stop-daemon --start --background --quiet --pidfile $TS_PIDFILE --exec $TS_DAEMON -- \
+        $TS_DAEMON_ARGS \
         || return 2
   # Add code here, if necessary, that waits for the process to be ready
   # to handle requests from services started subsequently which depend
   # on this one.  As a last resort, sleep for some time.
     sleep 1
-    test -f "$TM_PIDFILE" || return 2
+    test -f "$TS_PIDFILE" || return 2
 }
 
 #
@@ -252,7 +248,7 @@
   #   1 if daemon was already stopped
   #   2 if daemon could not be stopped
   #   other if a failure occurred
-    start-stop-daemon --stop --quiet --retry=QUIT/30/KILL/5 --pidfile $TM_PIDFILE --name $TM_NAME
+    start-stop-daemon --stop --quiet --retry=QUIT/30/KILL/5 --pidfile $TS_PIDFILE --name $TS_NAME
     RETVAL="$?"
     test "$RETVAL" != 0 && return $RETVAL
   # Wait for children to finish too if this is a daemon that forks
@@ -261,19 +257,14 @@
   # that waits for the process to drop all resources that could be
   # needed by services started subsequently.  A last resort is to
   # sleep for some time.
-    start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $TM_DAEMON
+    start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $TS_DAEMON
     RETVAL="$?"
     test "$RETVAL" != 0 && return $RETVAL
-  # Need to stop the TM and TS also
-    start-stop-daemon --stop --quiet --oknodo --retry=QUIT/30/KILL/5 --pidfile $TM_PIDFILE --name $TM_NAME
-    RETVAL="$?"
-    test "$RETVAL" != 0 && return $RETVAL
+  # Need to stop the TS
     start-stop-daemon --stop --quiet --oknodo --retry=QUIT/30/KILL/5 --pidfile $TS_PIDFILE --name $TS_NAME
     RETVAL="$?"
     test "$RETVAL" != 0 && return $RETVAL
   # Many daemons don't delete their pidfiles when they exit.
-    rm -f $TM_PIDFILE
-    rm -f $TM_PIDFILE
     rm -f $TS_PIDFILE
     return "$RETVAL"
 }
@@ -322,24 +313,24 @@
 	    do_start
 	    eend $?
         elif [ "$DISTRIB_ID" = "fedora" -o "$DISTRIB_ID" = "redhat" ]; then
-            action "Starting ${TS_PACKAGE_NAME}:" forkdaemon $TM_DAEMON $TM_DAEMON_ARGS
+            action "Starting ${TS_PACKAGE_NAME}:" forkdaemon $TS_DAEMON $TS_DAEMON_ARGS
         elif [ "$DISTRIB_ID" = "suse" ]; then
             echo -n "Starting ${TS_PACKAGE_NAME}"
-            startproc -p $TM_PIDFILE $TM_DAEMON $TM_DAEMON_ARGS
+            startproc -p $TS_PIDFILE $TS_DAEMON $TS_DAEMON_ARGS
             rc_status -v
         elif [ "$DISTRIB_ID" = "nixos" ]; then
             echo "Starting ${TS_PACKAGE_NAME}"
             forkdaemon $TM_DAEMON $TM_DAEMON_ARGS
         elif [ "$DISTRIB_ID" = "Darwin" ]; then
             echo "Starting ${TS_PACKAGE_NAME}"
-            launchctl bsexec / launchctl list $TM_NAME > /dev/null 2>&1 && exit 0
-            launchctl bsexec / launchctl submit -l $TM_NAME -p $TM_DAEMON -o $STDOUTLOG -e $STDERRLOG -- $TM_DAEMON_ARGS
+            launchctl bsexec / launchctl list $TS_NAME > /dev/null 2>&1 && exit 0
+            launchctl bsexec / launchctl submit -l $TS_NAME -p $TS_DAEMON -o $STDOUTLOG -e $STDERRLOG -- $TS_DAEMON_ARGS
         elif [ "$DISTRIB_ID" = "FreeBSD" ]; then
             echo "Starting ${TS_PACKAGE_NAME}"
-            name="$TM_NAME"
+            name="$TS_NAME"
             command="/usr/sbin/daemon"
-            command_args="-o $STDOUTLOG $TM_DAEMON $TM_DAEMON_ARGS"
-            pidfile="$TM_PIDFILE"
+            command_args="-o $STDOUTLOG $TS_DAEMON $TS_DAEMON_ARGS"
+            pidfile="$TS_PIDFILE"
             run_rc_command "$1"
         else
             echo "This script needs to be ported to this OS"
@@ -355,16 +346,12 @@
             test "x$VERBOSE" != "xno" && log_end_msg "$retval"
             exit "$retval"
         elif [ "$DISTRIB_ID" = "fedora" -o "$DISTRIB_ID" = "redhat" ]; then
-            action "Stopping ${TM_NAME}:" killproc -p $TM_PIDFILE -d 35 $TM_DAEMON
             action "Stopping ${TS_NAME}:" killproc -p $TS_PIDFILE -d 35 $TS_DAEMON
         elif [ "$DISTRIB_ID" = "gentoo" ]; then
 	    ebegin "Stopping ${TS_PACKAGE_NAME}"
 	    do_stop
 	    eend $?
         elif [ "$DISTRIB_ID" = "suse" ]; then
-            echo -n "Stopping ${TM_NAME}"
-            killproc -p $TM_PIDFILE $TM_DAEMON
-            rc_status -v
             echo -n "Stopping ${TS_NAME}"
             killproc -p $TS_PIDFILE $TS_DAEMON
             rc_status -v
@@ -380,18 +367,15 @@
             fi
         elif [ "$DISTRIB_ID" = "Darwin" ]; then
             echo "Stopping ${TS_PACKAGE_NAME}"
-            launchctl bsexec / launchctl list $TM_NAME > /dev/null 2>&1 || exit 0
-            echo "Stopping ${TM_NAME}"
-            launchctl bsexec / launchctl remove ${TM_NAME}
-            rm -f ${TM_PIDFILE}
+            launchctl bsexec / launchctl list $TS_NAME > /dev/null 2>&1 || exit 0
             echo "Stopping ${TS_NAME}"
-            kill $(cat $TS_PIDFILE)
+            launchctl bsexec / launchctl remove ${TS_NAME}
             rm -f ${TS_PIDFILE}
         elif [ "$DISTRIB_ID" = "FreeBSD" ]; then
             echo "Stopping ${TS_PACKAGE_NAME}"
-            if [ -e "$TM_PIDFILE" ]; then
-                kill $(cat $TM_PIDFILE)
-                rm -f ${TM_PIDFILE}
+            if [ -e "$TS_PIDFILE" ]; then
+                kill $(cat $TS_PIDFILE)
+                rm -f ${TS_PIDFILE}
             fi
         else
             echo "This script needs to be ported to this OS"
@@ -409,29 +393,29 @@
         if [ "$DISTRIB_ID" = "ubuntu" -o "$DISTRIB_ID" = "debian" ] ; then
             test "x$VERBOSE" != "xno" && log_daemon_msg "Reloading ${TS_PACKAGE_NAME}" "$NAME"
             retval=0
-            $TL_BINARY config reload
+            $TC_BINARY config reload
             test "$?" -ne 0 -a "$?" -ne 1 && retval=1
             test "x$VERBOSE" != "xno" && log_end_msg "$retval"
             exit "$retval"
         elif [ "$DISTRIB_ID" = "fedora" -o "$DISTRIB_ID" = "redhat" ]; then
-            action "Reloading ${NAME}:" $TL_BINARY config reload
+            action "Reloading ${NAME}:" $TC_BINARY config reload
         elif [ "$DISTRIB_ID" = "gentoo" ]; then
 	    ebegin "Reloading ${NAME}"
-            $TL_BINARY config reload
+            $TC_BINARY config reload
 	    eend $?
         elif [ "$DISTRIB_ID" = "suse" ]; then
             echo -n "Reloading ${NAME}"
-            $TL_BINARY config reload
+            $TC_BINARY config reload
             rc_status -v
         elif [ "$DISTRIB_ID" = "nixos" ]; then
             echo "Reloading ${NAME}"
             $TL_BINARY config reload
         elif [ "$DISTRIB_ID" = "Darwin" ]; then
             echo "Reloading ${NAME}"
-            $TL_BINARY config reload
+            $TC_BINARY config reload
         elif [ "$DISTRIB_ID" = "FreeBSD" ]; then
             echo "Reloading ${NAME}"
-            $TL_BINARY config reload
+            $TC_BINARY config reload
         else
             echo "This script needs to be ported to this OS"
             exit 1
@@ -469,24 +453,20 @@
         ;;
     status)
         if [ "$DISTRIB_ID" = "fedora" -o "$DISTRIB_ID" = "redhat" ]; then
-            status -p $TM_PIDFILE $TM_NAME
+            status -p $TS_PIDFILE $TS_NAME
         elif [ "$DISTRIB_ID" = "ubuntu" -o "$DISTRIB_ID" = "debian" ] ; then
-            status_of_proc "$TM_DAEMON" "$TM_NAME" -p "$TM_PIDFILE" && exit 0 || exit $?
+            status_of_proc "$TS_DAEMON" "$TS_NAME" -p "$TS_PIDFILE" && exit 0 || exit $?
         elif [  "$DISTRIB_ID" = "suse" ]; then
             echo -n "Checking for service ${DM}: "
-            checkproc -p $TM_PIDFILE $TM_NAME
+            checkproc -p $TS_PIDFILE $TS_NAME
             rc_status -v
         elif [ "$DISTRIB_ID" = "Darwin" ]; then
             /bin/echo -n "${TS_PACKAGE_NAME} is "
-            launchctl bsexec / launchctl list $TM_NAME > /dev/null 2>&1
+            launchctl bsexec / launchctl list $TS_NAME > /dev/null 2>&1
             status=$?
             [ $status -eq 0 ] || /bin/echo -n "not "
             echo "running."
         elif [ "$DISTRIB_ID" = "FreeBSD" -o "$DISTRIB_ID" = "gentoo" -o "$DISTRIB_ID" = "nixos" ]; then
-            if pgrep $TM_NAME > /dev/null ; then
-                echo "$TM_NAME running as pid `cat $TM_PIDFILE`" ; else
-                echo "$TM_NAME not running"
-            fi
             if pgrep $TS_NAME > /dev/null ; then
                 echo "$TS_NAME running as pid `cat $TS_PIDFILE`"; else
                 echo "$TS_NAME not running"
diff --git a/rc/trafficserver.service.in b/rc/trafficserver.service.in
index a503a49..e5ce1ab 100644
--- a/rc/trafficserver.service.in
+++ b/rc/trafficserver.service.in
@@ -22,11 +22,14 @@
 [Service]
 Type=simple
 EnvironmentFile=-/etc/sysconfig/trafficserver
-ExecStart=@exp_bindir@/traffic_manager $TM_DAEMON_ARGS
+ExecStart=@exp_bindir@/traffic_server $TS_DAEMON_ARGS
 Restart=on-failure
 RestartSec=5s
 LimitNOFILE=1000000
-PIDFile=@exp_runtimedir@/manager.lock
+ExecStopPost=/bin/sh -c ' \
+        export TS_PIDFILE=$(@exp_bindir@/traffic_layout 2>/dev/null | grep RUNTIMEDIR | cut -d: -f2)/server.lock ; \
+        /bin/rm -f $TS_PIDFILE ; \
+        if [[ $? -ne 0 ]]; then echo "ERROR: Unable to delete PID"; exit 1; fi'
 TimeoutStopSec=5s
 ExecReload=@exp_bindir@/traffic_ctl config reload
 KillMode=process
diff --git a/rc/trafficserver.xml.in b/rc/trafficserver.xml.in
index 0430cc5..e076fc9 100644
--- a/rc/trafficserver.xml.in
+++ b/rc/trafficserver.xml.in
@@ -62,12 +62,6 @@
 
 	<exec_method
 		type='method'
-		name='start'
-		exec='@exp_bindir@/traffic_manager 2>&amp;1 &amp;'
-		timeout_seconds='120' />
-
-	<exec_method
-		type='method'
 		name='stop'
 		exec=':kill'
 		timeout_seconds='120' />
diff --git a/src/Makefile.am b/src/Makefile.am
index b6a1b98..672ca28 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -33,13 +33,14 @@
 include traffic_cache_tool/Makefile.inc
 include traffic_via/Makefile.inc
 include traffic_top/Makefile.inc
-include traffic_manager/Makefile.inc
 include traffic_server/Makefile.inc
 include traffic_logstats/Makefile.inc
 include traffic_crashlog/Makefile.inc
-include traffic_ctl/Makefile.inc
 include traffic_layout/Makefile.inc
 include traffic_logcat/Makefile.inc
+include traffic_ctl/Makefile.inc
+
+
 
 if ENABLE_QUIC
 include traffic_quic/Makefile.inc
diff --git a/src/records/Makefile.am b/src/records/Makefile.am
index 1193b80..91cd7b5 100644
--- a/src/records/Makefile.am
+++ b/src/records/Makefile.am
@@ -21,6 +21,7 @@
 check_PROGRAMS = test_librecords test_librecords_on_eventsystem
 
 AM_CPPFLAGS += \
+        -I$(abs_top_srcdir)/lib/swoc/include \
 	-I$(abs_top_srcdir)/iocore/eventsystem \
 	-I$(abs_top_srcdir)/iocore/utils \
 	-I$(abs_top_srcdir)/mgmt \
@@ -28,18 +29,16 @@
 	-I$(abs_top_srcdir)/mgmt/utils \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
+	@SWOC_INCLUDES@\
 	$(TS_INCLUDES)
 
-noinst_LIBRARIES = librecords_lm.a librecords_p.a
+noinst_LIBRARIES = librecords_p.a
 
 librecords_COMMON = \
-	I_RecAlarms.h \
 	I_RecCore.h \
 	I_RecDefs.h \
-	I_RecEvents.h \
 	I_RecHttp.h \
 	I_RecMutex.h \
-	I_RecSignals.h \
 	P_RecCore.cc \
 	P_RecCore.h \
 	P_RecDefs.h \
@@ -56,12 +55,6 @@
 	RecRawStats.cc \
 	RecUtils.cc
 
-librecords_lm_a_SOURCES = \
-	$(librecords_COMMON) \
-	I_RecLocal.h \
-	P_RecLocal.h \
-	RecLocal.cc
-
 librecords_p_a_SOURCES = \
 	$(librecords_COMMON) \
 	I_RecProcess.h \
@@ -84,7 +77,7 @@
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	@HWLOC_LIBS@ @LIBCAP@
+	@SWOC_LIBS@ @HWLOC_LIBS@ @LIBCAP@
 
 test_librecords_on_eventsystem_CPPFLAGS = $(AM_CPPFLAGS)\
 	-I$(abs_top_srcdir)/tests/include
diff --git a/src/records/P_RecCore.cc b/src/records/P_RecCore.cc
index 09c608d..dca06da 100644
--- a/src/records/P_RecCore.cc
+++ b/src/records/P_RecCore.cc
@@ -39,228 +39,6 @@
 RecModeT g_mode_type = RECM_NULL;
 
 //-------------------------------------------------------------------------
-// send_reset_message
-//-------------------------------------------------------------------------
-static RecErrT
-send_reset_message(RecRecord *record)
-{
-  RecMessage *m;
-
-  rec_mutex_acquire(&(record->lock));
-  m = RecMessageAlloc(RECG_RESET);
-  m = RecMessageMarshal_Realloc(m, record);
-  RecDebug(DL_Note, "[send] RECG_RESET [%d bytes]", sizeof(RecMessageHdr) + m->o_write - m->o_start);
-  RecMessageSend(m);
-  RecMessageFree(m);
-  rec_mutex_release(&(record->lock));
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// send_set_message
-//-------------------------------------------------------------------------
-static RecErrT
-send_set_message(RecRecord *record)
-{
-  RecMessage *m;
-
-  rec_mutex_acquire(&(record->lock));
-  m = RecMessageAlloc(RECG_SET);
-  m = RecMessageMarshal_Realloc(m, record);
-  RecDebug(DL_Note, "[send] RECG_SET [%d bytes]", sizeof(RecMessageHdr) + m->o_write - m->o_start);
-  RecMessageSend(m);
-  RecMessageFree(m);
-  rec_mutex_release(&(record->lock));
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// send_register_message
-//-------------------------------------------------------------------------
-RecErrT
-send_register_message(RecRecord *record)
-{
-  RecMessage *m;
-
-  rec_mutex_acquire(&(record->lock));
-  m = RecMessageAlloc(RECG_REGISTER);
-  m = RecMessageMarshal_Realloc(m, record);
-  RecDebug(DL_Note, "[send] RECG_REGISTER [%d bytes]", sizeof(RecMessageHdr) + m->o_write - m->o_start);
-  RecMessageSend(m);
-  RecMessageFree(m);
-  rec_mutex_release(&(record->lock));
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// send_push_message
-//-------------------------------------------------------------------------
-RecErrT
-send_push_message()
-{
-  RecRecord *r;
-  RecMessage *m;
-  int i, num_records;
-  bool send_msg = false;
-
-  m           = RecMessageAlloc(RECG_PUSH);
-  num_records = g_num_records;
-  for (i = 0; i < num_records; i++) {
-    r = &(g_records[i]);
-    rec_mutex_acquire(&(r->lock));
-    if (i_am_the_record_owner(r->rec_type)) {
-      if (r->sync_required & REC_PEER_SYNC_REQUIRED) {
-        m = RecMessageMarshal_Realloc(m, r);
-        r->sync_required &= ~REC_PEER_SYNC_REQUIRED;
-        send_msg = true;
-      }
-    }
-    rec_mutex_release(&(r->lock));
-  }
-  if (send_msg) {
-    RecDebug(DL_Note, "[send] RECG_PUSH [%d bytes]", sizeof(RecMessageHdr) + m->o_write - m->o_start);
-    RecMessageSend(m);
-  }
-  RecMessageFree(m);
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// send_pull_message
-//-------------------------------------------------------------------------
-RecErrT
-send_pull_message(RecMessageT msg_type)
-{
-  RecRecord *r;
-  RecMessage *m;
-  int i, num_records;
-
-  m = RecMessageAlloc(msg_type);
-  switch (msg_type) {
-  case RECG_PULL_REQ:
-    // We're requesting all of the records from our peer.  No payload
-    // here, just send the message.
-    RecDebug(DL_Note, "[send] RECG_PULL_REQ [%d bytes]", sizeof(RecMessageHdr) + m->o_write - m->o_start);
-    break;
-
-  case RECG_PULL_ACK:
-    // Respond to a RECG_PULL_REQ message from our peer.  Send ALL
-    // records!  Also be sure to send a response even if it has no
-    // payload.  Our peer may be blocking and waiting for a response!
-    num_records = g_num_records;
-    for (i = 0; i < num_records; i++) {
-      r = &(g_records[i]);
-      if (i_am_the_record_owner(r->rec_type) || (REC_TYPE_IS_STAT(r->rec_type) && !(r->registered)) ||
-          (REC_TYPE_IS_STAT(r->rec_type) && (r->stat_meta.persist_type == RECP_NON_PERSISTENT))) {
-        rec_mutex_acquire(&(r->lock));
-        m = RecMessageMarshal_Realloc(m, r);
-        r->sync_required &= ~REC_PEER_SYNC_REQUIRED;
-        rec_mutex_release(&(r->lock));
-      }
-    }
-    RecDebug(DL_Note, "[send] RECG_PULL_ACK [%d bytes]", sizeof(RecMessageHdr) + m->o_write - m->o_start);
-    break;
-
-  default:
-    RecMessageFree(m);
-    return REC_ERR_FAIL;
-  }
-
-  RecMessageSend(m);
-  RecMessageFree(m);
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// recv_message_cb
-//-------------------------------------------------------------------------
-RecErrT
-recv_message_cb(RecMessage *msg, RecMessageT msg_type, void * /* cookie */)
-{
-  RecRecord *r;
-  RecMessageItr itr;
-
-  switch (msg_type) {
-  case RECG_SET:
-
-    RecDebug(DL_Note, "[recv] RECG_SET [%d bytes]", sizeof(RecMessageHdr) + msg->o_end - msg->o_start);
-    if (RecMessageUnmarshalFirst(msg, &itr, &r) != REC_ERR_FAIL) {
-      do {
-        if (REC_TYPE_IS_STAT(r->rec_type)) {
-          RecSetRecord(r->rec_type, r->name, r->data_type, &(r->data), &(r->stat_meta.data_raw), REC_SOURCE_EXPLICIT);
-        } else {
-          RecSetRecord(r->rec_type, r->name, r->data_type, &(r->data), nullptr, REC_SOURCE_EXPLICIT);
-        }
-      } while (RecMessageUnmarshalNext(msg, &itr, &r) != REC_ERR_FAIL);
-    }
-    break;
-
-  case RECG_RESET:
-
-    RecDebug(DL_Note, "[recv] RECG_RESET [%d bytes]", sizeof(RecMessageHdr) + msg->o_end - msg->o_start);
-    if (RecMessageUnmarshalFirst(msg, &itr, &r) != REC_ERR_FAIL) {
-      do {
-        if (REC_TYPE_IS_STAT(r->rec_type)) {
-          RecResetStatRecord(r->name);
-        } else {
-          RecSetRecord(r->rec_type, r->name, r->data_type, &(r->data), nullptr, REC_SOURCE_EXPLICIT);
-        }
-      } while (RecMessageUnmarshalNext(msg, &itr, &r) != REC_ERR_FAIL);
-    }
-    break;
-
-  case RECG_REGISTER:
-    RecDebug(DL_Note, "[recv] RECG_REGISTER [%d bytes]", sizeof(RecMessageHdr) + msg->o_end - msg->o_start);
-    if (RecMessageUnmarshalFirst(msg, &itr, &r) != REC_ERR_FAIL) {
-      do {
-        if (REC_TYPE_IS_STAT(r->rec_type)) {
-          RecRegisterStat(r->rec_type, r->name, r->data_type, r->data_default, r->stat_meta.persist_type);
-        } else if (REC_TYPE_IS_CONFIG(r->rec_type)) {
-          RecRegisterConfig(r->rec_type, r->name, r->data_type, r->data_default, r->config_meta.update_type,
-                            r->config_meta.check_type, r->config_meta.check_expr, r->config_meta.source,
-                            r->config_meta.access_type);
-        }
-      } while (RecMessageUnmarshalNext(msg, &itr, &r) != REC_ERR_FAIL);
-    }
-    break;
-
-  case RECG_PUSH:
-    RecDebug(DL_Note, "[recv] RECG_PUSH [%d bytes]", sizeof(RecMessageHdr) + msg->o_end - msg->o_start);
-    if (RecMessageUnmarshalFirst(msg, &itr, &r) != REC_ERR_FAIL) {
-      do {
-        RecForceInsert(r);
-      } while (RecMessageUnmarshalNext(msg, &itr, &r) != REC_ERR_FAIL);
-    }
-    break;
-
-  case RECG_PULL_ACK:
-    RecDebug(DL_Note, "[recv] RECG_PULL_ACK [%d bytes]", sizeof(RecMessageHdr) + msg->o_end - msg->o_start);
-    if (RecMessageUnmarshalFirst(msg, &itr, &r) != REC_ERR_FAIL) {
-      do {
-        RecForceInsert(r);
-      } while (RecMessageUnmarshalNext(msg, &itr, &r) != REC_ERR_FAIL);
-    }
-    break;
-
-  case RECG_PULL_REQ:
-    RecDebug(DL_Note, "[recv] RECG_PULL_REQ [%d bytes]", sizeof(RecMessageHdr) + msg->o_end - msg->o_start);
-    send_pull_message(RECG_PULL_ACK);
-    break;
-
-  default:
-    ink_assert(!"Unexpected RecG type");
-    return REC_ERR_FAIL;
-  }
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
 // RecRegisterStatXXX
 //-------------------------------------------------------------------------
 #define REC_REGISTER_STAT_XXX(A, B)                                                                                           \
@@ -271,8 +49,6 @@
   if ((r = RecRegisterStat(rec_type, name, B, my_data_default, persist_type)) != nullptr) {                                   \
     if (i_am_the_record_owner(r->rec_type)) {                                                                                 \
       r->sync_required = r->sync_required | REC_PEER_SYNC_REQUIRED;                                                           \
-    } else {                                                                                                                  \
-      send_register_message(r);                                                                                               \
     }                                                                                                                         \
     return REC_ERR_OKAY;                                                                                                      \
   } else {                                                                                                                    \
@@ -314,8 +90,6 @@
       nullptr) {                                                                                                                \
     if (i_am_the_record_owner(r->rec_type)) {                                                                                   \
       r->sync_required = r->sync_required | REC_PEER_SYNC_REQUIRED;                                                             \
-    } else {                                                                                                                    \
-      send_register_message(r);                                                                                                 \
     }                                                                                                                           \
     return REC_ERR_OKAY;                                                                                                        \
   } else {                                                                                                                      \
@@ -400,23 +174,6 @@
         }
       }
       rec_mutex_release(&(r1->lock));
-    } else {
-      // We don't need to ats_strdup() here as we will make copies of any
-      // strings when we marshal them into our RecMessage buffer.
-      RecRecord r2;
-
-      RecRecordInit(&r2);
-      r2.rec_type  = rec_type;
-      r2.name      = name;
-      r2.data_type = (data_type != RECD_NULL) ? data_type : r1->data_type;
-      r2.data      = *data;
-      if (REC_TYPE_IS_STAT(r2.rec_type) && (data_raw != nullptr)) {
-        r2.stat_meta.data_raw = *data_raw;
-      } else if (REC_TYPE_IS_CONFIG(r2.rec_type)) {
-        r2.config_meta.source = source;
-      }
-      err = send_set_message(&r2);
-      RecRecordFree(&r2);
     }
   } else {
     // Add the record but do not set the 'registered' flag, as this
@@ -437,9 +194,8 @@
     }
     if (i_am_the_record_owner(r1->rec_type)) {
       r1->sync_required = r1->sync_required | REC_PEER_SYNC_REQUIRED;
-    } else {
-      err = send_set_message(r1);
     }
+    // else, error if  from rec_type?
     g_records_ht.emplace(name, r1);
   }
 
@@ -697,24 +453,13 @@
 static RecErrT
 reset_stat_record(RecRecord *rec)
 {
-  RecErrT err;
+  RecErrT err = REC_ERR_FAIL;
 
   if (i_am_the_record_owner(rec->rec_type)) {
     rec_mutex_acquire(&(rec->lock));
     ++(rec->version);
     err = RecDataSet(rec->data_type, &(rec->data), &(rec->data_default)) ? REC_ERR_OKAY : REC_ERR_FAIL;
     rec_mutex_release(&(rec->lock));
-  } else {
-    RecRecord r2;
-
-    RecRecordInit(&r2);
-    r2.rec_type  = rec->rec_type;
-    r2.name      = rec->name;
-    r2.data_type = rec->data_type;
-    r2.data      = rec->data_default;
-
-    err = send_reset_message(&r2);
-    RecRecordFree(&r2);
   }
 
   return err;
@@ -789,24 +534,6 @@
       }
       rec_mutex_release(&(r1->lock));
       err = REC_ERR_OKAY;
-    } else {
-      // No point of doing the following because our peer will
-      // set the value with RecDataSet. However, since
-      // r2.name == r1->name, the sync_required bit will not be
-      // set.
-
-      /*
-         RecRecord r2;
-
-         RecRecordInit(&r2);
-         r2.rec_type  = r1->rec_type;
-         r2.name      = r1->name;
-         r2.data_type = r1->data_type;
-         r2.data      = r1->data_default;
-
-         err = send_set_message(&r2);
-         RecRecordFree(&r2);
-       */
     }
   }
 
diff --git a/src/records/RecCore.cc b/src/records/RecCore.cc
index 005f9dd..8f9dbdd 100644
--- a/src/records/RecCore.cc
+++ b/src/records/RecCore.cc
@@ -1262,22 +1262,6 @@
   return Layout::relative_to(rundir, ts::filename::RECORDS_STATS);
 }
 
-void
-RecSignalWarning(int sig, const char *fmt, ...)
-{
-  char msg[1024];
-  va_list args;
-
-  va_start(args, fmt);
-  WarningV(fmt, args);
-  va_end(args);
-
-  va_start(args, fmt);
-  vsnprintf(msg, sizeof(msg), fmt, args);
-  RecSignalManager(sig, msg);
-  va_end(args);
-}
-
 //-------------------------------------------------------------------------
 // RecConfigWarnIfUnregistered
 //-------------------------------------------------------------------------
diff --git a/src/records/RecHttp.cc b/src/records/RecHttp.cc
index 7e9a62d..a2e7cb5 100644
--- a/src/records/RecHttp.cc
+++ b/src/records/RecHttp.cc
@@ -26,12 +26,15 @@
 #include "tscore/ink_defs.h"
 #include "tscore/TextBuffer.h"
 #include "tscore/Tokenizer.h"
+#include <cstring>
 #include <strings.h>
 #include "tscore/ink_inet.h"
 #include <string_view>
 #include <unordered_set>
 #include <tscore/IpMapConf.h>
 
+using ts::TextView;
+
 SessionProtocolNameRegistry globalSessionProtocolNameRegistry;
 
 /* Protocol session well-known protocol names.
@@ -44,8 +47,8 @@
 const char *const TS_ALPN_PROTOCOL_HTTP_2_0      = IP_PROTO_TAG_HTTP_2_0.data();
 const char *const TS_ALPN_PROTOCOL_HTTP_3        = IP_PROTO_TAG_HTTP_3.data();
 const char *const TS_ALPN_PROTOCOL_HTTP_QUIC     = IP_PROTO_TAG_HTTP_QUIC.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_3_D27    = IP_PROTO_TAG_HTTP_3_D27.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D27 = IP_PROTO_TAG_HTTP_QUIC_D27.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_3_D29    = IP_PROTO_TAG_HTTP_3_D29.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D29 = IP_PROTO_TAG_HTTP_QUIC_D29.data();
 
 const char *const TS_ALPN_PROTOCOL_GROUP_HTTP  = "http";
 const char *const TS_ALPN_PROTOCOL_GROUP_HTTP2 = "http2";
@@ -55,8 +58,8 @@
 const char *const TS_PROTO_TAG_HTTP_2_0      = TS_ALPN_PROTOCOL_HTTP_2_0;
 const char *const TS_PROTO_TAG_HTTP_3        = TS_ALPN_PROTOCOL_HTTP_3;
 const char *const TS_PROTO_TAG_HTTP_QUIC     = TS_ALPN_PROTOCOL_HTTP_QUIC;
-const char *const TS_PROTO_TAG_HTTP_3_D27    = TS_ALPN_PROTOCOL_HTTP_3_D27;
-const char *const TS_PROTO_TAG_HTTP_QUIC_D27 = TS_ALPN_PROTOCOL_HTTP_QUIC_D27;
+const char *const TS_PROTO_TAG_HTTP_3_D29    = TS_ALPN_PROTOCOL_HTTP_3_D29;
+const char *const TS_PROTO_TAG_HTTP_QUIC_D29 = TS_ALPN_PROTOCOL_HTTP_QUIC_D29;
 const char *const TS_PROTO_TAG_TLS_1_3       = IP_PROTO_TAG_TLS_1_3.data();
 const char *const TS_PROTO_TAG_TLS_1_2       = IP_PROTO_TAG_TLS_1_2.data();
 const char *const TS_PROTO_TAG_TLS_1_1       = IP_PROTO_TAG_TLS_1_1.data();
@@ -75,8 +78,8 @@
 int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0      = SessionProtocolNameRegistry::INVALID;
 int TS_ALPN_PROTOCOL_INDEX_HTTP_3        = SessionProtocolNameRegistry::INVALID;
 int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC     = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27    = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27 = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_3_D29    = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D29 = SessionProtocolNameRegistry::INVALID;
 
 // Predefined protocol sets for ease of use.
 SessionProtocolSet HTTP_PROTOCOL_SET;
@@ -720,10 +723,10 @@
   TS_ALPN_PROTOCOL_INDEX_HTTP_1_1   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_1});
   TS_ALPN_PROTOCOL_INDEX_HTTP_2_0   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_2_0});
   TS_ALPN_PROTOCOL_INDEX_HTTP_3     = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3_D27});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_3_D29 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3_D29});
   TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27 =
-    globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC_D27});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D29 =
+    globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC_D29});
 
   // Now do the predefined protocol sets.
   HTTP_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_0_9);
@@ -737,8 +740,8 @@
 
   DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3);
   DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC);
-  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27);
-  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27);
+  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3_D29);
+  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D29);
 
   DEFAULT_NON_TLS_SESSION_PROTOCOL_SET = HTTP_PROTOCOL_SET;
 
@@ -747,8 +750,8 @@
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_2_0);
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_3);
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC);
-  TSProtoTags.insert(TS_PROTO_TAG_HTTP_3_D27);
-  TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC_D27);
+  TSProtoTags.insert(TS_PROTO_TAG_HTTP_3_D29);
+  TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC_D29);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_3);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_2);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_1);
@@ -841,3 +844,87 @@
 {
   return 0 <= idx && idx < m_n ? m_names[idx] : TextView{};
 }
+
+bool
+convert_alpn_to_wire_format(std::string_view protocols_sv, unsigned char *wire_format_buffer, int &wire_format_buffer_len)
+{
+  // TODO: once the protocols_sv is switched to be a TextView (see the TODO
+  // comment in this functions doxygen comment), then rename the input
+  // parameter to be simply `protocols` and remove this next line.
+  TextView protocols(protocols_sv);
+  // Callers expect wire_format_buffer_len to be zero'd out in the event of an
+  // error. To simplify the error handling from doing this on every return, we
+  // simply zero them out here at the start.
+  auto const orig_wire_format_buffer_len = wire_format_buffer_len;
+  memset(wire_format_buffer, 0, wire_format_buffer_len);
+  wire_format_buffer_len = 0;
+
+  if (protocols.empty()) {
+    return false;
+  }
+
+  // Parse the comma separated protocol string into a list of protocol names.
+  std::vector<std::string_view> alpn_protocols;
+  TextView protocol;
+  int computed_alpn_array_len = 0;
+
+  while (protocols) {
+    protocol = protocols.take_prefix_at(',').trim_if(&isspace);
+    if (protocol.empty()) {
+      Error("Empty protocol name in configured ALPN list: \"%.*s\"", static_cast<int>(protocols.size()), protocols.data());
+      return false;
+    }
+    if (protocol.size() > 255) {
+      // The length has to fit in one byte.
+      Error("A protocol name larger than 255 bytes in configured ALPN list: \"%.*s\"", static_cast<int>(protocols.size()),
+            protocols.data());
+      return false;
+    }
+    // Check whether we recognize the protocol.
+    auto const protocol_index = globalSessionProtocolNameRegistry.indexFor(protocol);
+    if (protocol_index == SessionProtocolNameRegistry::INVALID) {
+      Error("Unknown protocol name in configured ALPN list: \"%.*s\"", static_cast<int>(protocol.size()), protocol.data());
+      return false;
+    }
+    // We currently only support HTTP/1.x protocols toward the origin.
+    if (!HTTP_PROTOCOL_SET.contains(protocol_index)) {
+      Error("Unsupported non-HTTP/1.x protocol name in configured ALPN list: \"%.*s\"", static_cast<int>(protocol.size()),
+            protocol.data());
+      return false;
+    }
+    // But not HTTP/0.9.
+    if (protocol_index == TS_ALPN_PROTOCOL_INDEX_HTTP_0_9) {
+      Error("Unsupported \"http/0.9\" protocol name in configured ALPN list: \"%.*s\"", static_cast<int>(protocol.size()),
+            protocol.data());
+      return false;
+    }
+
+    auto const protocol_wire_format = globalSessionProtocolNameRegistry.convert_openssl_alpn_wire_format(protocol_index);
+    computed_alpn_array_len += protocol_wire_format.size();
+    if (computed_alpn_array_len > orig_wire_format_buffer_len) {
+      // We have exceeded the size of the output buffer.
+      Error("The output ALPN length (%d bytes) is larger than the output buffer size of %d bytes", computed_alpn_array_len,
+            orig_wire_format_buffer_len);
+      return false;
+    }
+
+    alpn_protocols.push_back(protocol_wire_format);
+  }
+  if (alpn_protocols.empty()) {
+    Error("No protocols specified in ALPN list: \"%.*s\"", static_cast<int>(protocols.size()), protocols.data());
+    return false;
+  }
+
+  // All checks pass and the protocols are parsed. Write the result to the
+  // output buffer.
+  auto *end = wire_format_buffer;
+  for (auto &protocol : alpn_protocols) {
+    auto const len = protocol.size();
+    memcpy(end, protocol.data(), len);
+    end += len;
+  }
+  wire_format_buffer_len = computed_alpn_array_len;
+  Debug("ssl_alpn", "Successfully converted ALPN list to wire format: \"%.*s\"", static_cast<int>(protocols.size()),
+        protocols.data());
+  return true;
+}
diff --git a/src/records/RecLocal.cc b/src/records/RecLocal.cc
deleted file mode 100644
index 510daa2..0000000
--- a/src/records/RecLocal.cc
+++ /dev/null
@@ -1,214 +0,0 @@
-/** @file
-
-  Record local definitions
-
-  @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 "tscore/ink_platform.h"
-#include "ConfigManager.h"
-#include "tscore/ParseRules.h"
-#include "records/P_RecCore.h"
-#include "records/P_RecLocal.h"
-#include "records/P_RecMessage.h"
-#include "records/P_RecUtils.h"
-#include "records/P_RecFile.h"
-#include "LocalManager.h"
-#include "FileManager.h"
-#include <tscore/TSSystemState.h>
-
-// Marks whether the message handler has been initialized.
-static bool message_initialized_p = false;
-
-//-------------------------------------------------------------------------
-// i_am_the_record_owner, only used for libreclocal.a
-//-------------------------------------------------------------------------
-bool
-i_am_the_record_owner(RecT rec_type)
-{
-  switch (rec_type) {
-  case RECT_CONFIG:
-  case RECT_NODE:
-  case RECT_LOCAL:
-    return true;
-  case RECT_PROCESS:
-  case RECT_PLUGIN:
-    return false;
-  default:
-    ink_assert(!"Unexpected RecT type");
-    return false;
-  }
-}
-
-//-------------------------------------------------------------------------
-// sync_thr
-//-------------------------------------------------------------------------
-static void *
-sync_thr(void *data)
-{
-  FileManager *configFiles = static_cast<FileManager *>(data);
-
-  while (!TSSystemState::is_event_system_shut_down()) {
-    send_push_message();
-    RecSyncStatsFile();
-
-    // If we didn't successfully sync to disk, check whether we need to update ....
-    bool found;
-    int track_time = static_cast<int>(REC_readInteger("proxy.config.track_config_files", &found));
-    if (found && track_time > 0) {
-      if (configFiles->isConfigStale()) {
-        RecSetRecordInt("proxy.node.config.reconfigure_required", 1, REC_SOURCE_DEFAULT);
-      }
-    }
-
-    usleep(REC_REMOTE_SYNC_INTERVAL_MS * 1000);
-  }
-
-  return nullptr;
-}
-
-//-------------------------------------------------------------------------
-// config_update_thr
-//-------------------------------------------------------------------------
-static void *
-config_update_thr(void * /* data */)
-{
-  while (!TSSystemState::is_event_system_shut_down()) {
-    switch (RecExecConfigUpdateCbs(REC_LOCAL_UPDATE_REQUIRED)) {
-    case RECU_RESTART_TS:
-      RecSetRecordInt("proxy.node.config.restart_required.proxy", 1, REC_SOURCE_DEFAULT);
-      break;
-    case RECU_RESTART_TM:
-      RecSetRecordInt("proxy.node.config.restart_required.proxy", 1, REC_SOURCE_DEFAULT);
-      RecSetRecordInt("proxy.node.config.restart_required.manager", 1, REC_SOURCE_DEFAULT);
-      break;
-    case RECU_NULL:
-    case RECU_DYNAMIC:
-      break;
-    }
-
-    usleep(REC_CONFIG_UPDATE_INTERVAL_MS * 1000);
-  }
-  return nullptr;
-}
-
-//-------------------------------------------------------------------------
-// RecMessageInit
-//-------------------------------------------------------------------------
-void
-RecMessageInit()
-{
-  ink_assert(g_mode_type != RECM_NULL);
-  lmgmt->registerMgmtCallback(MGMT_SIGNAL_LIBRECORDS, &RecMessageRecvThis);
-  message_initialized_p = true;
-}
-
-//-------------------------------------------------------------------------
-// RecLocalInit
-//-------------------------------------------------------------------------
-int
-RecLocalInit(Diags *_diags)
-{
-  static bool initialized_p = false;
-  ;
-
-  if (initialized_p) {
-    return REC_ERR_OKAY;
-  }
-
-  g_mode_type = RECM_SERVER;
-
-  if (RecCoreInit(RECM_SERVER, _diags) == REC_ERR_FAIL) {
-    return REC_ERR_FAIL;
-  }
-
-  initialized_p = true;
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// RecLocalInitMessage
-//-------------------------------------------------------------------------
-int
-RecLocalInitMessage()
-{
-  static bool initialized_p = false;
-
-  if (initialized_p) {
-    return REC_ERR_OKAY;
-  }
-
-  RecMessageInit();
-  if (RecMessageRegisterRecvCb(recv_message_cb, nullptr)) {
-    return REC_ERR_FAIL;
-  }
-
-  initialized_p = true;
-
-  return REC_ERR_OKAY;
-}
-
-//-------------------------------------------------------------------------
-// RecLocalStart
-//-------------------------------------------------------------------------
-int
-RecLocalStart(FileManager *configFiles)
-{
-  ink_thread_create(nullptr, sync_thr, configFiles, 0, 0, nullptr);
-  ink_thread_create(nullptr, config_update_thr, nullptr, 0, 0, nullptr);
-  return REC_ERR_OKAY;
-}
-
-int
-RecRegisterManagerCb(int id, RecManagerCb const &_fn)
-{
-  return lmgmt->registerMgmtCallback(id, _fn);
-}
-
-void
-RecSignalManager(int id, const char *, size_t)
-{
-  // Signals are messages sent across the management pipe, so by definition,
-  // you can't send a signal if you are a local process manager.
-  RecDebug(DL_Debug, "local manager dropping signal %d", id);
-}
-
-//-------------------------------------------------------------------------
-// RecMessageSend
-//-------------------------------------------------------------------------
-
-int
-RecMessageSend(RecMessage *msg)
-{
-  int msg_size;
-
-  if (!message_initialized_p) {
-    return REC_ERR_OKAY;
-  }
-
-  // Make a copy of the record, but truncate it to the size actually used
-  if (g_mode_type == RECM_CLIENT || g_mode_type == RECM_SERVER) {
-    msg->o_end = msg->o_write;
-    msg_size   = sizeof(RecMessageHdr) + (msg->o_write - msg->o_start);
-    lmgmt->signalEvent(MGMT_EVENT_LIBRECORDS, reinterpret_cast<char *>(msg), msg_size);
-  }
-
-  return REC_ERR_OKAY;
-}
diff --git a/src/records/RecMessage.cc b/src/records/RecMessage.cc
index 15916e1..2c4ec43 100644
--- a/src/records/RecMessage.cc
+++ b/src/records/RecMessage.cc
@@ -31,7 +31,7 @@
 #include "records/P_RecUtils.h"
 #include "records/P_RecCore.h"
 #include "tscore/I_Layout.h"
-#include "tscpp/util/MemSpan.h"
+#include "swoc/MemSpan.h"
 
 static RecMessageRecvCb g_recv_cb = nullptr;
 static void *g_recv_cookie        = nullptr;
@@ -241,17 +241,6 @@
 }
 
 //-------------------------------------------------------------------------
-// RecMessageRecvThis
-//-------------------------------------------------------------------------
-
-void
-RecMessageRecvThis(ts::MemSpan<void> span)
-{
-  RecMessage *msg = static_cast<RecMessage *>(span.data());
-  g_recv_cb(msg, msg->msg_type, g_recv_cookie);
-}
-
-//-------------------------------------------------------------------------
 // RecMessageReadFromDisk
 //-------------------------------------------------------------------------
 
diff --git a/src/records/RecProcess.cc b/src/records/RecProcess.cc
index 86fbd73..c046fb2 100644
--- a/src/records/RecProcess.cc
+++ b/src/records/RecProcess.cc
@@ -33,9 +33,6 @@
 #include "records/P_RecUtils.h"
 #include "records/P_RecFile.h"
 
-#include "mgmtapi.h"
-#include "ProcessManager.h"
-
 // Marks whether the message handler has been initialized.
 static bool message_initialized_p = false;
 static bool g_started             = false;
@@ -53,20 +50,7 @@
 bool
 i_am_the_record_owner(RecT rec_type)
 {
-  if (g_mode_type == RECM_CLIENT) {
-    switch (rec_type) {
-    case RECT_PROCESS:
-    case RECT_PLUGIN:
-      return true;
-    case RECT_CONFIG:
-    case RECT_NODE:
-    case RECT_LOCAL:
-      return false;
-    default:
-      ink_assert(!"Unexpected RecT type");
-      return false;
-    }
-  } else if (g_mode_type == RECM_STAND_ALONE) {
+  if (g_mode_type == RECM_STAND_ALONE) {
     switch (rec_type) {
     case RECT_CONFIG:
     case RECT_PROCESS:
@@ -118,24 +102,6 @@
 }
 
 //-------------------------------------------------------------------------
-// recv_message_cb__process
-//-------------------------------------------------------------------------
-static RecErrT
-recv_message_cb__process(RecMessage *msg, RecMessageT msg_type, void *cookie)
-{
-  RecErrT err;
-
-  if ((err = recv_message_cb(msg, msg_type, cookie)) == REC_ERR_OKAY) {
-    if (msg_type == RECG_PULL_ACK) {
-      g_force_req_notify.lock();
-      g_force_req_notify.signal();
-      g_force_req_notify.unlock();
-    }
-  }
-  return err;
-}
-
-//-------------------------------------------------------------------------
 // raw_stat_sync_cont
 //-------------------------------------------------------------------------
 struct raw_stat_sync_cont : public Continuation {
@@ -188,7 +154,6 @@
   int
   sync(int /* event */, Event * /* e */)
   {
-    send_push_message();
     RecSyncStatsFile();
 
     Debug("statsproc", "sync_cont() processed");
@@ -224,10 +189,8 @@
 RecMessageInit()
 {
   ink_assert(g_mode_type != RECM_NULL);
-  pmgmt->registerMgmtCallback(MGMT_EVENT_LIBRECORDS, &RecMessageRecvThis);
   message_initialized_p = true;
 }
-
 //-------------------------------------------------------------------------
 // RecProcessInitMessage
 //-------------------------------------------------------------------------
@@ -241,17 +204,6 @@
   }
 
   RecMessageInit();
-  if (RecMessageRegisterRecvCb(recv_message_cb__process, nullptr)) {
-    return REC_ERR_FAIL;
-  }
-
-  if (mode_type == RECM_CLIENT) {
-    send_pull_message(RECG_PULL_REQ);
-    g_force_req_notify.lock();
-    g_force_req_notify.wait();
-    g_force_req_notify.unlock();
-  }
-
   initialized_p = true;
 
   return REC_ERR_OKAY;
@@ -284,39 +236,3 @@
 
   return REC_ERR_OKAY;
 }
-
-void
-RecSignalManager(int id, const char *msg, size_t msgsize)
-{
-  ink_assert(pmgmt);
-  pmgmt->signalManager(id, msg, msgsize);
-}
-
-int
-RecRegisterManagerCb(int _signal, RecManagerCb const &_fn)
-{
-  return pmgmt->registerMgmtCallback(_signal, _fn);
-}
-
-//-------------------------------------------------------------------------
-// RecMessageSend
-//-------------------------------------------------------------------------
-
-int
-RecMessageSend(RecMessage *msg)
-{
-  int msg_size;
-
-  if (!message_initialized_p) {
-    return REC_ERR_OKAY;
-  }
-
-  // Make a copy of the record, but truncate it to the size actually used
-  if (g_mode_type == RECM_CLIENT || g_mode_type == RECM_SERVER) {
-    msg->o_end = msg->o_write;
-    msg_size   = sizeof(RecMessageHdr) + (msg->o_write - msg->o_start);
-    pmgmt->signalManager(MGMT_SIGNAL_LIBRECORDS, reinterpret_cast<char *>(msg), msg_size);
-  }
-
-  return REC_ERR_OKAY;
-}
diff --git a/src/records/RecRawStats.cc b/src/records/RecRawStats.cc
index 59c98f7..622c1d5 100644
--- a/src/records/RecRawStats.cc
+++ b/src/records/RecRawStats.cc
@@ -267,8 +267,6 @@
   r->rsb_id = id; // This is the index within the RSB raw block for this stat, used for lookups by name.
   if (i_am_the_record_owner(r->rec_type)) {
     r->sync_required = r->sync_required | REC_PEER_SYNC_REQUIRED;
-  } else {
-    send_register_message(r);
   }
 
   // store a pointer to our record->stat_meta.data_raw in our rsb
diff --git a/src/records/test_I_RecLocal.cc b/src/records/test_I_RecLocal.cc
index 056f1d1..4cba95a 100644
--- a/src/records/test_I_RecLocal.cc
+++ b/src/records/test_I_RecLocal.cc
@@ -21,7 +21,6 @@
   limitations under the License.
  */
 
-#include "I_RecLocal.h"
 #include "P_RecUtils.h"
 #include "test_RecordsConfig.h"
 
diff --git a/src/records/test_RecProcess.i b/src/records/test_RecProcess.i
index e9c3c91..70c756b 100644
--- a/src/records/test_RecProcess.i
+++ b/src/records/test_RecProcess.i
@@ -593,8 +593,6 @@
   eventProcessor.start(4);
   RecProcessStart();
 
-  RecSignalManager(1, "This is a signal, signaled by RecSignalManager");
-
   // See if we're sync'd okay
   RecDumpRecordsHt(RECT_NULL);
 
diff --git a/src/records/unit_tests/test_RecHttp.cc b/src/records/unit_tests/test_RecHttp.cc
index a93b1e9..6dcf4f0 100644
--- a/src/records/unit_tests/test_RecHttp.cc
+++ b/src/records/unit_tests/test_RecHttp.cc
@@ -18,15 +18,17 @@
    the License.
  */
 
+#include <array>
 #include <string>
 #include <string_view>
-#include <array>
+#include <vector>
 
 #include "catch.hpp"
 
 #include "tscore/BufferWriter.h"
 #include "records/I_RecHttp.h"
 #include "test_Diags.h"
+#include "tscore/ink_defs.h"
 
 using ts::TextView;
 
@@ -97,3 +99,131 @@
     REQUIRE(view.find(":proto") == TextView::npos); // it's default, should not have this.
   }
 }
+
+struct ConvertAlpnToWireFormatTestCase {
+  std::string description;
+  std::string alpn_input;
+  unsigned char expected_alpn_wire_format[MAX_ALPN_STRING] = {0};
+  int expected_alpn_wire_format_len                        = MAX_ALPN_STRING;
+  bool expected_return                                     = true;
+};
+
+// clang-format off
+std::vector<ConvertAlpnToWireFormatTestCase> convertAlpnToWireFormatTestCases = {
+  // --------------------------------------------------------------------------
+  // Malformed input.
+  // --------------------------------------------------------------------------
+  {
+    "Empty input protocol list",
+    "",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "Include an empty protocol in the list",
+    "http/1.1,,http/1.0",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "A protocol that exceeds the output buffer length (MAX_ALPN_STRING)",
+    "some_really_long_protocol_name_that_exceeds_the_output_buffer_length_that_is_MAX_ALPN_STRING",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "The sum of protocols exceeds the output buffer length (MAX_ALPN_STRING)",
+    "protocol_one,protocol_two,protocol_three",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "A protocol that exceeds the length described by a single byte (255)",
+    "some_really_long_protocol_name_that_exceeds_255_bytes_some_really_long_protocol_name_that_exceeds_255_bytes_some_really_long_protocol_name_that_exceeds_255_bytes_some_really_long_protocol_name_that_exceeds_255_bytes_some_really_long_protocol_name_that_exceeds_255_bytes",
+    { 0 },
+    0,
+    false
+  },
+  // --------------------------------------------------------------------------
+  // Unsupported protocols.
+  // --------------------------------------------------------------------------
+  {
+    "Unrecognized protocol: HTTP/6",
+    "h6",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "Single protocol: HTTP/0.9",
+    "http/0.9",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "Single protocol: HTTP/2 (currently unsupported)",
+    "h2",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "Single protocol: HTTP/3 (currently unsupported)",
+    "h3",
+    { 0 },
+    0,
+    false
+  },
+  {
+    "Both HTTP/1.1 and HTTP/2 (HTTP/2 is currently unsupported)",
+    "h2,http/1.1",
+    { 0 },
+    0,
+    false
+  },
+  // --------------------------------------------------------------------------
+  // Happy cases.
+  // --------------------------------------------------------------------------
+  {
+    "Single protocol: HTTP/1.1",
+    "http/1.1",
+    {0x08, 'h', 't', 't', 'p', '/', '1', '.', '1'},
+    9,
+    true
+  },
+  {
+    "Multiple protocols: HTTP/1.0, HTTP/1.1",
+    "http/1.1,http/1.0",
+    {0x08, 'h', 't', 't', 'p', '/', '1', '.', '1', 0x08, 'h', 't', 't', 'p', '/', '1', '.', '0'},
+    18,
+    true
+  },
+  {
+    "Whitespace: verify that we gracefully handle padded whitespace",
+    "http/1.1, http/1.0",
+    {0x08, 'h', 't', 't', 'p', '/', '1', '.', '1', 0x08, 'h', 't', 't', 'p', '/', '1', '.', '0'},
+    18,
+    true
+  },
+};
+// clang-format on
+
+TEST_CASE("convert_alpn_to_wire_format", "[librecords][RecHttp]")
+{
+  for (auto const &test_case : convertAlpnToWireFormatTestCases) {
+    SECTION(test_case.description)
+    {
+      unsigned char alpn_wire_format[MAX_ALPN_STRING] = {0xab};
+      int alpn_wire_format_len                        = MAX_ALPN_STRING;
+      auto const result = convert_alpn_to_wire_format(test_case.alpn_input, alpn_wire_format, alpn_wire_format_len);
+      REQUIRE(result == test_case.expected_return);
+      REQUIRE(alpn_wire_format_len == test_case.expected_alpn_wire_format_len);
+      REQUIRE(memcmp(alpn_wire_format, test_case.expected_alpn_wire_format, test_case.expected_alpn_wire_format_len) == 0);
+    }
+  }
+}
diff --git a/src/shared/overridable_txn_vars.cc b/src/shared/overridable_txn_vars.cc
index f4d2a04..9a89064 100644
--- a/src/shared/overridable_txn_vars.cc
+++ b/src/shared/overridable_txn_vars.cc
@@ -89,8 +89,6 @@
      {OutboundConnTrack::CONFIG_VAR_MIN, {TS_CONFIG_HTTP_SERVER_MIN_KEEP_ALIVE_CONNS, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.anonymize_remove_client_ip", {TS_CONFIG_HTTP_ANONYMIZE_REMOVE_CLIENT_IP, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.cache.open_read_retry_time", {TS_CONFIG_HTTP_CACHE_OPEN_READ_RETRY_TIME, TS_RECORDDATATYPE_INT}},
-     // TODO: Remove for 10.  Leaving for now to keep enums consistent
-     {"proxy.config.http.down_server.abort_threshold", {TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD, TS_RECORDDATATYPE_INT}},
      {OutboundConnTrack::CONFIG_VAR_MATCH, {TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.parent_proxy.fail_threshold", {TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.cache.ignore_authentication", {TS_CONFIG_HTTP_CACHE_IGNORE_AUTHENTICATION, TS_RECORDDATATYPE_INT}},
@@ -118,7 +116,6 @@
      {"proxy.config.http.cache.guaranteed_min_lifetime", {TS_CONFIG_HTTP_CACHE_GUARANTEED_MIN_LIFETIME, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.cache.guaranteed_max_lifetime", {TS_CONFIG_HTTP_CACHE_GUARANTEED_MAX_LIFETIME, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.transaction_active_timeout_in", {TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_IN, TS_RECORDDATATYPE_INT}},
-     {"proxy.config.http.post_connect_attempts_timeout", {TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.cache.ignore_client_cc_max_age", {TS_CONFIG_HTTP_CACHE_IGNORE_CLIENT_CC_MAX_AGE, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.negative_revalidating_lifetime", {TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIFETIME, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.transaction_active_timeout_out", {TS_CONFIG_HTTP_TRANSACTION_ACTIVE_TIMEOUT_OUT, TS_RECORDDATATYPE_INT}},
@@ -151,8 +148,6 @@
       {TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_LANGUAGE_MISMATCH, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.cache.ignore_accept_encoding_mismatch",
       {TS_CONFIG_HTTP_CACHE_IGNORE_ACCEPT_ENCODING_MISMATCH, TS_RECORDDATATYPE_INT}},
-     {"proxy.config.http.parent_proxy.connect_attempts_timeout",
-      {TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.connect_attempts_max_retries_dead_server",
       {TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DEAD_SERVER, TS_RECORDDATATYPE_INT}},
      {"proxy.config.http.parent_proxy.per_parent_connect_attempts",
@@ -165,6 +160,7 @@
      {"proxy.config.ssl.client.cert.path", {TS_CONFIG_SSL_CERT_FILEPATH, TS_RECORDDATATYPE_STRING}},
      {"proxy.config.ssl.client.private_key.filename", {TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME, TS_RECORDDATATYPE_STRING}},
      {"proxy.config.ssl.client.CA.cert.filename", {TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME, TS_RECORDDATATYPE_STRING}},
+     {"proxy.config.ssl.client.alpn_protocols", {TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS, TS_RECORDDATATYPE_STRING}},
      {"proxy.config.hostdb.ip_resolve", {TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE, TS_RECORDDATATYPE_STRING}},
      {"proxy.config.plugin.vc.default_buffer_index", {TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX, TS_RECORDDATATYPE_INT}},
      {"proxy.config.plugin.vc.default_buffer_water_mark", {TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK, TS_RECORDDATATYPE_INT}},
diff --git a/src/shared/rpc/IPCSocketClient.cc b/src/shared/rpc/IPCSocketClient.cc
new file mode 100644
index 0000000..d39cf3a
--- /dev/null
+++ b/src/shared/rpc/IPCSocketClient.cc
@@ -0,0 +1,95 @@
+/**
+  @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 "shared/rpc/IPCSocketClient.h"
+#include <stdexcept>
+#include <chrono>
+
+#include <tscore/ink_assert.h>
+#include <tscore/BufferWriter.h>
+
+namespace shared::rpc
+{
+IPCSocketClient::self_reference
+IPCSocketClient::connect()
+{
+  _sock = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (this->is_closed()) {
+    std::string text;
+    ts::bwprint(text, "connect: error creating new socket. Why?: {}\n", std::strerror(errno));
+    throw std::runtime_error{text};
+  }
+  _server.sun_family = AF_UNIX;
+  std::strncpy(_server.sun_path, _path.c_str(), sizeof(_server.sun_path) - 1);
+  if (::connect(_sock, (struct sockaddr *)&_server, sizeof(struct sockaddr_un)) < 0) {
+    this->close();
+    std::string text;
+    ts::bwprint(text, "connect: Couldn't open connection with {}. Why?: {}\n", _path, std::strerror(errno));
+    throw std::runtime_error{text};
+  }
+
+  return *this;
+}
+
+IPCSocketClient::self_reference
+IPCSocketClient ::send(std::string_view data)
+{
+  std::string msg{data};
+  if (::write(_sock, msg.c_str(), msg.size()) < 0) {
+    this->close();
+    std::string text;
+    throw std::runtime_error{ts::bwprint(text, "Error writing on stream socket {}", std ::strerror(errno))};
+  }
+
+  return *this;
+}
+
+IPCSocketClient::ReadStatus
+IPCSocketClient::read_all(ts::FixedBufferWriter &bw)
+{
+  if (this->is_closed()) {
+    // we had a failure.
+    return {};
+  }
+  ReadStatus readStatus{ReadStatus::UNKNOWN};
+  while (bw.remaining()) {
+    swoc::MemSpan<char> span{bw.auxBuffer(), bw.remaining()};
+    const ssize_t ret = ::read(_sock, span.data(), span.size());
+    if (ret > 0) {
+      bw.fill(ret);
+      if (bw.remaining() > 0) { // some space available.
+        continue;
+      } else {
+        // buffer full.
+        readStatus = ReadStatus::BUFFER_FULL;
+        break;
+      }
+    } else {
+      if (bw.size()) {
+        // data was read.
+        readStatus = ReadStatus::NO_ERROR;
+        break;
+      }
+      readStatus = ReadStatus::STREAM_ERROR;
+      break;
+    }
+  }
+  return readStatus;
+}
+} // namespace shared::rpc
diff --git a/src/traffic_cache_tool/CacheDefs.cc b/src/traffic_cache_tool/CacheDefs.cc
index ce7edeb..631563f 100644
--- a/src/traffic_cache_tool/CacheDefs.cc
+++ b/src/traffic_cache_tool/CacheDefs.cc
@@ -649,7 +649,7 @@
 
   while (p2) {
     // p1 moves by one entry per iteration
-    assert(p1);
+    ink_assert(p1);
     p1 = next_dir(p1, seg);
     // p2 moves by two entries per iteration
     p2 = next_dir(p2, seg);
@@ -762,7 +762,7 @@
           ++seg_empty;
           --seg_buckets_in_use;
           // this should only happen on the first dir in a bucket
-          assert(nullptr == next_dir(e, seg));
+          ink_assert(nullptr == next_dir(e, seg));
           break;
         } else {
           int e_idx = e - seg;
@@ -857,7 +857,7 @@
         // The size markings are redundant. Low values (less than DIR_SHIFT_WIDTH) for larger
         // base block sizes should never be used. Such entries should use the next smaller base block size.
         if (b > 0 && s < 1 << DIR_BLOCK_SHIFT(1)) {
-          assert(frag_demographics[s][b] == 0);
+          ink_assert(frag_demographics[s][b] == 0);
           continue;
         }
         printf(" %8d[%2d:%1d]:%06d", (s + 1) * block_size, s, b, frag_demographics[s][b]);
diff --git a/src/traffic_cache_tool/CacheDefs.h b/src/traffic_cache_tool/CacheDefs.h
index 24f4d2b..e69beca 100644
--- a/src/traffic_cache_tool/CacheDefs.h
+++ b/src/traffic_cache_tool/CacheDefs.h
@@ -28,7 +28,7 @@
 #include <list>
 
 #include "tscore/I_Version.h"
-#include "tscore/Scalar.h"
+#include "swoc/Scalar.h"
 #include "tscore/Regex.h"
 #include "tscore/Errata.h"
 #include "tscpp/util/TextView.h"
@@ -43,8 +43,8 @@
 };
 } // namespace ts::tag
 
-using ts::round_down;
-using ts::round_up;
+using swoc::round_down;
+using swoc::round_up;
 
 namespace ts
 {
@@ -76,19 +76,19 @@
 constexpr static int ENTRIES_PER_BUCKET      = 4;
 constexpr static int MAX_BUCKETS_PER_SEGMENT = (1 << 16) / ENTRIES_PER_BUCKET;
 
-typedef Scalar<1, off_t, ts::tag::bytes> Bytes;
-typedef Scalar<1024, off_t, ts::tag::bytes> Kilobytes;
-typedef Scalar<1024 * Kilobytes::SCALE, off_t, ts::tag::bytes> Megabytes;
-typedef Scalar<1024 * Megabytes::SCALE, off_t, ts::tag::bytes> Gigabytes;
-typedef Scalar<1024 * Gigabytes::SCALE, off_t, ts::tag::bytes> Terabytes;
+using Bytes     = swoc::Scalar<1, off_t, ts::tag::bytes>;
+using Kilobytes = swoc::Scalar<1024, off_t, ts::tag::bytes>;
+using Megabytes = swoc::Scalar<1024 * Kilobytes::SCALE, off_t, ts::tag::bytes>;
+using Gigabytes = swoc::Scalar<1024 * Megabytes::SCALE, off_t, ts::tag::bytes>;
+using Terabytes = swoc::Scalar<1024 * Gigabytes::SCALE, off_t, ts::tag::bytes>;
 
 // Units of allocation for stripes.
-typedef Scalar<128 * Megabytes::SCALE, int64_t, ts::tag::bytes> CacheStripeBlocks;
+using CacheStripeBlocks = swoc::Scalar<128 * Megabytes::SCALE, int64_t, ts::tag::bytes>;
 // Size measurement of cache storage.
 // Also size of meta data storage units.
-typedef Scalar<8 * Kilobytes::SCALE, int64_t, ts::tag::bytes> CacheStoreBlocks;
+using CacheStoreBlocks = swoc::Scalar<8 * Kilobytes::SCALE, int64_t, ts::tag::bytes>;
 // Size unit for content stored in cache.
-typedef Scalar<512, int64_t, ts::tag::bytes> CacheDataBlocks;
+using CacheDataBlocks = swoc::Scalar<512, int64_t, ts::tag::bytes>;
 
 /** A cache span is a representation of raw storage.
     It corresponds to a raw disk, disk partition, file, or directory.
@@ -350,7 +350,7 @@
 using ts::CacheStripeDescriptor;
 using ts::Errata;
 using ts::CacheDirEntry;
-using ts::MemSpan;
+using swoc::MemSpan;
 using ts::Doc;
 
 constexpr int ESTIMATED_OBJECT_SIZE     = 8000;
diff --git a/src/traffic_cache_tool/CacheScan.cc b/src/traffic_cache_tool/CacheScan.cc
index 34cb50c..207c1ab 100644
--- a/src/traffic_cache_tool/CacheScan.cc
+++ b/src/traffic_cache_tool/CacheScan.cc
@@ -29,7 +29,7 @@
 
 // using namespace ct;
 
-constexpr HdrHeapMarshalBlocks HTTP_ALT_MARSHAL_SIZE = ts::round_up(sizeof(HTTPCacheAlt));
+constexpr HdrHeapMarshalBlocks HTTP_ALT_MARSHAL_SIZE = swoc::round_up(sizeof(HTTPCacheAlt));
 
 namespace ct
 {
@@ -125,7 +125,7 @@
 {
   Errata zret;
   HDR_UNMARSHAL_PTR(mf->m_next, MIMEFieldBlockImpl, offset);
-  ts::MemSpan mf_mem(reinterpret_cast<char *>(mf), mf->m_length);
+  swoc::MemSpan mf_mem(reinterpret_cast<char *>(mf), mf->m_length);
   for (uint32_t index = 0; index < mf->m_freetop; index++) {
     MIMEField *field = &(mf->m_field_slots[index]);
 
@@ -248,7 +248,7 @@
 
   hh->m_magic = HDR_BUF_MAGIC_ALIVE;
 
-  return HdrHeapMarshalBlocks(ts::round_up(hh->unmarshal_size()));
+  return HdrHeapMarshalBlocks(swoc::round_up(hh->unmarshal_size()));
 }
 
 Errata
@@ -344,7 +344,7 @@
 
 // check if the url looks valid
 bool
-CacheScan::check_url(ts::MemSpan<char> &mem, URLImpl *url)
+CacheScan::check_url(swoc::MemSpan<char> &mem, URLImpl *url)
 {
   bool in_bound = false; // boolean to check if address in bound
   if (!url->m_ptr_scheme) {
@@ -365,7 +365,7 @@
 
   char *start            = const_cast<char *>(buf);
   RefCountObj *block_ref = nullptr;
-  ts::MemSpan<char> doc_mem(const_cast<char *>(buf), length);
+  swoc::MemSpan<char> doc_mem(const_cast<char *>(buf), length);
 
   while (length - (buf - start) > static_cast<int>(sizeof(HTTPCacheAlt))) {
     HTTPCacheAlt *a = (HTTPCacheAlt *)buf;
diff --git a/src/traffic_cache_tool/CacheScan.h b/src/traffic_cache_tool/CacheScan.h
index c593b70..6ab716a 100644
--- a/src/traffic_cache_tool/CacheScan.h
+++ b/src/traffic_cache_tool/CacheScan.h
@@ -58,6 +58,6 @@
   Errata unmarshal(URLImpl *obj, intptr_t offset);
   Errata unmarshal(MIMEFieldBlockImpl *mf, intptr_t offset);
   Errata unmarshal(MIMEHdrImpl *obj, intptr_t offset);
-  bool check_url(ts::MemSpan<char> &mem, URLImpl *url);
+  bool check_url(swoc::MemSpan<char> &mem, URLImpl *url);
 };
 } // namespace ct
diff --git a/src/traffic_cache_tool/CacheTool.cc b/src/traffic_cache_tool/CacheTool.cc
index 85ceced..5210cce 100644
--- a/src/traffic_cache_tool/CacheTool.cc
+++ b/src/traffic_cache_tool/CacheTool.cc
@@ -54,7 +54,7 @@
 using ts::CacheStripeDescriptor;
 using ts::Errata;
 using ts::CacheDirEntry;
-using ts::MemSpan;
+using swoc::MemSpan;
 using ts::Doc;
 
 enum { SILENT = 0, NORMAL, VERBOSE } Verbosity = NORMAL;
diff --git a/src/traffic_cache_tool/Makefile.inc b/src/traffic_cache_tool/Makefile.inc
index 2d972e3..71dc559 100644
--- a/src/traffic_cache_tool/Makefile.inc
+++ b/src/traffic_cache_tool/Makefile.inc
@@ -23,6 +23,7 @@
     $(AM_CPPFLAGS) \
     -I $(abs_top_srcdir)/include \
     -I $(abs_top_srcdir)/lib \
+    @SWOC_INCLUDES@ \
     -D__STDC_FORMAT_MACROS
 
 traffic_cache_tool_traffic_cache_tool_SOURCES = \
@@ -32,6 +33,8 @@
     traffic_cache_tool/CacheScan.h \
     traffic_cache_tool/CacheScan.cc
 
+traffic_cache_tool_traffic_cache_tool_LDFLAGS = $(AM_LDFLAGS) @SWOC_LDFLAGS@
+
 traffic_cache_tool_traffic_cache_tool_LDADD = \
     $(top_builddir)/src/tscore/.libs/ArgParser.o \
     $(top_builddir)/src/tscore/.libs/ink_assert.o \
@@ -45,7 +48,6 @@
     $(top_builddir)/src/tscore/.libs/ts_file.o \
     $(top_builddir)/src/tscore/.libs/Errata.o \
     $(top_builddir)/src/tscpp/util/.libs/TextView.o \
-    $(top_builddir)/src/tscpp/util/.libs/string_view_util.o \
     $(top_builddir)/src/tscore/.libs/Regex.o \
     $(top_builddir)/src/tscore/.libs/CryptoHash.o \
     $(top_builddir)/src/tscore/.libs/MMH.o \
@@ -54,4 +56,4 @@
     $(top_builddir)/src/tscore/.libs/ink_args.o \
     $(top_builddir)/src/tscore/.libs/ParseRules.o \
     $(top_builddir)/src/tscore/.libs/SourceLocation.o \
-    @OPENSSL_LIBS@ @LIBPCRE@
+    @SWOC_LIBS@ @OPENSSL_LIBS@ @LIBPCRE@
diff --git a/src/traffic_crashlog/Makefile.inc b/src/traffic_crashlog/Makefile.inc
index 7e79b6a..a69054b 100644
--- a/src/traffic_crashlog/Makefile.inc
+++ b/src/traffic_crashlog/Makefile.inc
@@ -25,6 +25,7 @@
     -I$(abs_top_srcdir)/mgmt \
     -I$(abs_top_srcdir)/mgmt/utils \
     -I$(abs_top_srcdir)/mgmt/api/include \
+    @SWOC_INCLUDES@ \
     $(TS_INCLUDES)
 
 traffic_crashlog_traffic_crashlog_LDFLAGS = \
@@ -42,7 +43,6 @@
 	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
 	$(top_builddir)/iocore/net/libinknet.a \
-	$(top_builddir)/mgmt/api/libtsmgmt.la \
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	@HWLOC_LIBS@
diff --git a/src/traffic_crashlog/procinfo.cc b/src/traffic_crashlog/procinfo.cc
index 1180e03..db99549 100644
--- a/src/traffic_crashlog/procinfo.cc
+++ b/src/traffic_crashlog/procinfo.cc
@@ -142,77 +142,6 @@
 }
 
 bool
-crashlog_write_backtrace(FILE *fp, const crashlog_target &)
-{
-  TSString trace = nullptr;
-  TSMgmtError mgmterr;
-
-  // NOTE: sometimes we can't get a backtrace because the ptrace attach will fail with
-  // EPERM. I've seen this happen when a debugger is attached, which makes sense, but it
-  // can also happen without a debugger. Possibly in that case, there is a race with the
-  // kernel locking the process information?
-
-  if ((mgmterr = TSProxyBacktraceGet(0, &trace)) != TS_ERR_OKAY) {
-    char *msg = TSGetErrorMessage(mgmterr);
-    fprintf(fp, "Unable to retrieve backtrace: %s\n", msg);
-    TSfree(msg);
-    return false;
-  }
-
-  fprintf(fp, "%s", trace);
-  TSfree(trace);
-  return true;
-}
-
-bool
-crashlog_write_records(FILE *fp, const crashlog_target &)
-{
-  TSMgmtError mgmterr;
-  TSList list  = TSListCreate();
-  bool success = false;
-
-  if ((mgmterr = TSRecordGetMatchMlt(".", list)) != TS_ERR_OKAY) {
-    char *msg = TSGetErrorMessage(mgmterr);
-    fprintf(fp, "Unable to retrieve Traffic Server records: %s\n", msg);
-    TSfree(msg);
-    goto done;
-  }
-
-  // If the RPC call failed, the list will be empty, so we won't print anything. Otherwise,
-  // print all the results, freeing them as we go.
-  for (TSRecordEle *rec_ele = (TSRecordEle *)TSListDequeue(list); rec_ele; rec_ele = (TSRecordEle *)TSListDequeue(list)) {
-    if (!success) {
-      success = true;
-      fprintf(fp, "Traffic Server Configuration Records:\n");
-    }
-
-    switch (rec_ele->rec_type) {
-    case TS_REC_INT:
-      fprintf(fp, "%s %" PRId64 "\n", rec_ele->rec_name, rec_ele->valueT.int_val);
-      break;
-    case TS_REC_COUNTER:
-      fprintf(fp, "%s %" PRId64 "\n", rec_ele->rec_name, rec_ele->valueT.counter_val);
-      break;
-    case TS_REC_FLOAT:
-      fprintf(fp, "%s %f\n", rec_ele->rec_name, rec_ele->valueT.float_val);
-      break;
-    case TS_REC_STRING:
-      fprintf(fp, "%s %s\n", rec_ele->rec_name, rec_ele->valueT.string_val);
-      break;
-    default:
-      // just skip it ...
-      break;
-    }
-
-    TSRecordEleDestroy(rec_ele);
-  }
-
-done:
-  TSListDestroy(list);
-  return success;
-}
-
-bool
 crashlog_write_siginfo(FILE *fp, const crashlog_target &target)
 {
   char tmp[32];
diff --git a/src/traffic_crashlog/traffic_crashlog.cc b/src/traffic_crashlog/traffic_crashlog.cc
index 6828293..245245e 100644
--- a/src/traffic_crashlog/traffic_crashlog.cc
+++ b/src/traffic_crashlog/traffic_crashlog.cc
@@ -134,7 +134,6 @@
 {
   FILE *fp;
   char *logname;
-  TSMgmtError mgmterr;
   crashlog_target target;
   pid_t parent = getppid();
 
@@ -195,13 +194,6 @@
   Note("crashlog started, target=%ld, debug=%s syslog=%s, uid=%ld euid=%ld", static_cast<long>(target_pid),
        debug_mode ? "true" : "false", syslog_mode ? "true" : "false", (long)getuid(), (long)geteuid());
 
-  mgmterr = TSInit(nullptr, (TSInitOptionT)(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
-  if (mgmterr != TS_ERR_OKAY) {
-    char *msg = TSGetErrorMessage(mgmterr);
-    Warning("failed to initialize management API: %s", msg);
-    TSfree(msg);
-  }
-
   ink_zero(target);
   target.pid       = static_cast<pid_t>(target_pid);
   target.timestamp = timestamp();
@@ -247,9 +239,6 @@
   crashlog_write_registers(fp, target);
 
   fprintf(fp, "\n");
-  crashlog_write_backtrace(fp, target);
-
-  fprintf(fp, "\n");
   crashlog_write_procstatus(fp, target);
 
   fprintf(fp, "\n");
@@ -258,9 +247,6 @@
   fprintf(fp, "\n");
   crashlog_write_regions(fp, target);
 
-  fprintf(fp, "\n");
-  crashlog_write_records(fp, target);
-
   Error("wrote crash log to %s", logname);
 
   ats_free(logname);
diff --git a/src/traffic_crashlog/traffic_crashlog.h b/src/traffic_crashlog/traffic_crashlog.h
index ba17f5a..37dded2 100644
--- a/src/traffic_crashlog/traffic_crashlog.h
+++ b/src/traffic_crashlog/traffic_crashlog.h
@@ -27,7 +27,6 @@
 #include "tscore/ink_memory.h"
 #include "tscore/Diags.h"
 #include "tscore/TextBuffer.h"
-#include "mgmtapi.h"
 
 // ucontext.h is deprecated on Darwin, and we really only need it on Linux, so only
 // include it if we are planning to use it.
diff --git a/src/traffic_ctl/CtrlCommands.cc b/src/traffic_ctl/CtrlCommands.cc
new file mode 100644
index 0000000..7db72f7
--- /dev/null
+++ b/src/traffic_ctl/CtrlCommands.cc
@@ -0,0 +1,518 @@
+/** @file
+
+  @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 <fstream>
+#include <unordered_map>
+
+#include "CtrlCommands.h"
+#include "jsonrpc/CtrlRPCRequests.h"
+#include "jsonrpc/ctrl_yaml_codecs.h"
+namespace
+{
+/// We use yamlcpp as codec implementation.
+using Codec = yamlcpp_json_emitter;
+
+const std::unordered_map<std::string_view, BasePrinter::Options::OutputFormat> _Fmt_str_to_enum = {
+  {"pretty", BasePrinter::Options::OutputFormat::PRETTY},
+  {"legacy", BasePrinter::Options::OutputFormat::LEGACY},
+  {"json", BasePrinter::Options::OutputFormat::JSON},
+  {"rpc", BasePrinter::Options::OutputFormat::RPC}};
+
+BasePrinter::Options::OutputFormat
+parse_format(ts::Arguments *args)
+{
+  if (args->get("records")) {
+    return BasePrinter::Options::OutputFormat::RECORDS;
+  }
+
+  BasePrinter::Options::OutputFormat val{BasePrinter::Options::OutputFormat::LEGACY};
+
+  if (auto data = args->get("format"); data) {
+    ts::TextView fmt{data.value()};
+    if (auto search = _Fmt_str_to_enum.find(fmt); search != std::end(_Fmt_str_to_enum)) {
+      val = search->second;
+    }
+  }
+  return val;
+}
+
+BasePrinter::Options
+parse_print_opts(ts::Arguments *args)
+{
+  return {parse_format(args)};
+}
+} // namespace
+
+//------------------------------------------------------------------------------------------------------------------------------------
+CtrlCommand::CtrlCommand(ts::Arguments *args) : _arguments(args) {}
+
+void
+CtrlCommand::execute()
+{
+  if (_invoked_func) {
+    _invoked_func();
+  } else {
+    throw std::logic_error("CtrlCommand::execute(): Internal error. There should be a function to invoke. (_invoked_func not set)");
+  }
+}
+
+std::string
+CtrlCommand::invoke_rpc(std::string const &request)
+{
+  if (_printer->print_rpc_message()) {
+    std::string text;
+    ts::bwprint(text, "--> {}", request);
+    _printer->write_debug(std::string_view{text});
+  }
+  if (auto resp = _rpcClient.invoke(request); !resp.empty()) {
+    // all good.
+    if (_printer->print_rpc_message()) {
+      std::string text;
+      ts::bwprint(text, "<-- {}", resp);
+      _printer->write_debug(std::string_view{text});
+    }
+    return resp;
+  }
+
+  return {};
+}
+
+shared::rpc::JSONRPCResponse
+CtrlCommand::invoke_rpc(shared::rpc::ClientRequest const &request)
+{
+  std::string encodedRequest = Codec::encode(request);
+  std::string resp           = invoke_rpc(encodedRequest);
+  return Codec::decode(resp);
+}
+
+void
+CtrlCommand::invoke_rpc(shared::rpc::ClientRequest const &request, std::string &resp)
+{
+  std::string encodedRequest = Codec::encode(request);
+  resp                       = invoke_rpc(encodedRequest);
+}
+// -----------------------------------------------------------------------------------------------------------------------------------
+ConfigCommand::ConfigCommand(ts::Arguments *args) : RecordCommand(args)
+{
+  BasePrinter::Options printOpts{parse_print_opts(args)};
+  if (args->get(MATCH_STR)) {
+    _printer      = std::make_unique<RecordPrinter>(printOpts);
+    _invoked_func = [&]() { config_match(); };
+  } else if (args->get(GET_STR)) {
+    _printer      = std::make_unique<RecordPrinter>(printOpts);
+    _invoked_func = [&]() { config_get(); };
+  } else if (args->get(DIFF_STR)) {
+    _printer      = std::make_unique<DiffConfigPrinter>(printOpts);
+    _invoked_func = [&]() { config_diff(); };
+  } else if (args->get(DESCRIBE_STR)) {
+    _printer      = std::make_unique<RecordDescribePrinter>(printOpts);
+    _invoked_func = [&]() { config_describe(); };
+  } else if (args->get(DEFAULTS_STR)) {
+    _printer      = std::make_unique<RecordPrinter>(printOpts);
+    _invoked_func = [&]() { config_defaults(); };
+  } else if (args->get(SET_STR)) {
+    _printer      = std::make_unique<ConfigSetPrinter>(printOpts);
+    _invoked_func = [&]() { config_set(); };
+  } else if (args->get(STATUS_STR)) {
+    _printer      = std::make_unique<RecordPrinter>(printOpts);
+    _invoked_func = [&]() { config_status(); };
+  } else if (args->get(RELOAD_STR)) {
+    _printer      = std::make_unique<ConfigReloadPrinter>(printOpts);
+    _invoked_func = [&]() { config_reload(); };
+  } else if (args->get(REGISTRY_STR)) {
+    _printer      = std::make_unique<ConfigShowFileRegistryPrinter>(printOpts);
+    _invoked_func = [&]() { config_show_file_registry(); };
+  } else {
+    // work in here.
+  }
+}
+
+shared::rpc::JSONRPCResponse
+RecordCommand::record_fetch(ts::ArgumentData argData, bool isRegex, RecordQueryType recQueryType)
+{
+  shared::rpc::RecordLookupRequest request;
+  for (auto &&it : argData) {
+    request.emplace_rec(it, isRegex,
+                        recQueryType == RecordQueryType::CONFIG ? shared::rpc::CONFIG_REC_TYPES : shared::rpc::METRIC_REC_TYPES);
+  }
+  return invoke_rpc(request);
+}
+
+void
+ConfigCommand::config_match()
+{
+  _printer->write_output(record_fetch(get_parsed_arguments()->get(MATCH_STR), shared::rpc::REGEX, RecordQueryType::CONFIG));
+}
+
+void
+ConfigCommand::config_get()
+{
+  _printer->write_output(record_fetch(get_parsed_arguments()->get(GET_STR), shared::rpc::NOT_REGEX, RecordQueryType::CONFIG));
+}
+
+void
+ConfigCommand::config_describe()
+{
+  _printer->write_output(record_fetch(get_parsed_arguments()->get(DESCRIBE_STR), shared::rpc::NOT_REGEX, RecordQueryType::CONFIG));
+}
+void
+ConfigCommand::config_defaults()
+{
+  const bool configs{true};
+  shared::rpc::JSONRPCResponse response = invoke_rpc(GetAllRecordsRequest{configs});
+  _printer->write_output(response);
+}
+void
+ConfigCommand::config_diff()
+{
+  GetAllRecordsRequest request{true};
+  shared::rpc::JSONRPCResponse response = invoke_rpc(request);
+  _printer->write_output(response);
+}
+
+void
+ConfigCommand::config_status()
+{
+  ConfigStatusRequest request;
+  shared::rpc::JSONRPCResponse response = invoke_rpc(request);
+  _printer->write_output(response);
+}
+
+void
+ConfigCommand::config_set()
+{
+  auto const &data = get_parsed_arguments()->get(SET_STR);
+  ConfigSetRecordRequest request{{data[0], data[1]}};
+  shared::rpc::JSONRPCResponse response = invoke_rpc(request);
+
+  _printer->write_output(response);
+}
+void
+ConfigCommand::config_reload()
+{
+  _printer->write_output(invoke_rpc(ConfigReloadRequest{}));
+}
+void
+ConfigCommand::config_show_file_registry()
+{
+  _printer->write_output(invoke_rpc(ConfigShowFileRegistryRequest{}));
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+MetricCommand::MetricCommand(ts::Arguments *args) : RecordCommand(args)
+{
+  BasePrinter::Options printOpts{parse_print_opts(args)};
+  if (args->get(MATCH_STR)) {
+    _printer      = std::make_unique<MetricRecordPrinter>(printOpts);
+    _invoked_func = [&]() { metric_match(); };
+  } else if (args->get(GET_STR)) {
+    _printer      = std::make_unique<MetricRecordPrinter>(printOpts);
+    _invoked_func = [&]() { metric_get(); };
+  } else if (args->get(DESCRIBE_STR)) {
+    _printer      = std::make_unique<RecordDescribePrinter>(printOpts);
+    _invoked_func = [&]() { metric_describe(); };
+  } else if (args->get(CLEAR_STR)) {
+    _printer      = std::make_unique<GenericPrinter>(printOpts);
+    _invoked_func = [&]() { metric_clear(); };
+  } else if (args->get(ZERO_STR)) {
+    _printer      = std::make_unique<GenericPrinter>(printOpts);
+    _invoked_func = [&]() { metric_zero(); };
+  }
+}
+
+void
+MetricCommand::metric_get()
+{
+  _printer->write_output(record_fetch(get_parsed_arguments()->get(GET_STR), shared::rpc::NOT_REGEX, RecordQueryType::METRIC));
+}
+
+void
+MetricCommand::metric_match()
+{
+  _printer->write_output(record_fetch(get_parsed_arguments()->get(MATCH_STR), shared::rpc::REGEX, RecordQueryType::METRIC));
+}
+
+void
+MetricCommand::metric_describe()
+{
+  _printer->write_output(record_fetch(get_parsed_arguments()->get(DESCRIBE_STR), shared::rpc::NOT_REGEX, RecordQueryType::METRIC));
+}
+
+void
+MetricCommand::metric_clear()
+{
+  [[maybe_unused]] auto const &response = invoke_rpc(ClearAllMetricRequest{});
+}
+
+void
+MetricCommand::metric_zero()
+{
+  auto records = get_parsed_arguments()->get(ZERO_STR);
+  ClearMetricRequest request{// names
+                             {{std::begin(records), std::end(records)}}};
+
+  [[maybe_unused]] auto const &response = invoke_rpc(request);
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+// TODO, let call the super const
+HostCommand::HostCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  BasePrinter::Options printOpts{parse_print_opts(args)};
+  if (get_parsed_arguments()->get(STATUS_STR)) {
+    _printer      = std::make_unique<GetHostStatusPrinter>(printOpts);
+    _invoked_func = [&]() { status_get(); };
+  } else if (get_parsed_arguments()->get(DOWN_STR)) {
+    _printer      = std::make_unique<SetHostStatusPrinter>(printOpts);
+    _invoked_func = [&]() { status_down(); };
+  } else if (get_parsed_arguments()->get(UP_STR)) {
+    _printer      = std::make_unique<SetHostStatusPrinter>(printOpts);
+    _invoked_func = [&]() { status_up(); };
+  }
+}
+
+void
+HostCommand::status_get()
+{
+  auto const &data = get_parsed_arguments()->get(STATUS_STR);
+  HostGetStatusRequest request{{std::begin(data), std::end(data)}};
+
+  auto response = invoke_rpc(request);
+
+  _printer->write_output(response);
+}
+
+void
+HostCommand::status_down()
+{
+  auto hosts = get_parsed_arguments()->get(DOWN_STR);
+  HostSetStatusRequest request{{HostSetStatusRequest::Params::Op::DOWN,
+                                {std::begin(hosts), std::end(hosts)},
+                                get_parsed_arguments()->get(REASON_STR).value(),
+                                "0"}};
+  auto response = invoke_rpc(request);
+  _printer->write_output(response);
+}
+
+void
+HostCommand::status_up()
+{
+  auto hosts = get_parsed_arguments()->get(UP_STR);
+  HostSetStatusRequest request{{HostSetStatusRequest::Params::Op::UP,
+                                {std::begin(hosts), std::end(hosts)},
+                                get_parsed_arguments()->get(REASON_STR).value(),
+                                "0"}};
+
+  auto response = invoke_rpc(request);
+  _printer->write_output(response);
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+PluginCommand::PluginCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  if (get_parsed_arguments()->get(MSG_STR)) {
+    _invoked_func = [&]() { plugin_msg(); };
+  }
+  _printer = std::make_unique<GenericPrinter>(parse_print_opts(args));
+}
+
+void
+PluginCommand::plugin_msg()
+{
+  auto msgs = get_parsed_arguments()->get(MSG_STR);
+  BasicPluginMessageRequest::Params params;
+  params.tag = msgs[0];
+  if (msgs.size() > 1) {
+    // have a value
+    params.str = msgs[1];
+  }
+  BasicPluginMessageRequest request{params};
+  auto response = invoke_rpc(request);
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+DirectRPCCommand::DirectRPCCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  BasePrinter::Options printOpts{parse_print_opts(args)};
+
+  if (get_parsed_arguments()->get(GET_API_STR)) {
+    _printer      = std::make_unique<RPCAPIPrinter>(printOpts);
+    _invoked_func = [&]() { get_rpc_api(); };
+    return;
+  } else if (get_parsed_arguments()->get(FILE_STR)) {
+    _invoked_func = [&]() { from_file_request(); };
+  } else if (get_parsed_arguments()->get(INPUT_STR)) {
+    _invoked_func = [&]() { read_from_input(); };
+  } else if (get_parsed_arguments()->get(INVOKE_STR)) {
+    _invoked_func = [&]() { invoke_method(); };
+    if (printOpts._format == BasePrinter::Options::OutputFormat::LEGACY) {
+      // overwrite this and let it drop json instead.
+      printOpts._format = BasePrinter::Options::OutputFormat::RPC;
+    }
+  }
+
+  _printer = std::make_unique<GenericPrinter>(printOpts);
+}
+
+bool
+DirectRPCCommand::validate_input(std::string const &in) const
+{
+  // validate the input
+  YAML::Node content = YAML::Load(in);
+  if (content.Type() != YAML::NodeType::Map && content.Type() != YAML::NodeType::Sequence) {
+    return false;
+  }
+
+  return true;
+}
+
+void
+DirectRPCCommand::from_file_request()
+{
+  // TODO: remove all the output messages from here if possible
+  auto filenames = get_parsed_arguments()->get(FILE_STR);
+  for (auto &&filename : filenames) {
+    std::string text;
+    // run some basic validation on the passed files, they should
+    try {
+      std::ifstream file(filename);
+      std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
+
+      if (!validate_input(content)) {
+        _printer->write_output(
+          ts::bwprint(text, "Content not accepted. expecting a valid sequence or structure. {} skipped.\n", filename));
+        continue;
+      }
+      std::string const &response = invoke_rpc(content);
+      if (_printer->is_json_format()) {
+        // as we have the raw json in here, we cna just directly print it
+        _printer->write_output(response);
+      } else {
+        _printer->write_output(ts::bwprint(text, "\n[ {} ]\n --> \n{}\n", filename, content));
+        _printer->write_output(ts::bwprint(text, "<--\n{}\n", response));
+      }
+
+    } catch (std::exception const &ex) {
+      _printer->write_output(ts::bwprint(text, "Error found: {}\n", ex.what()));
+    }
+  }
+}
+
+void
+DirectRPCCommand::get_rpc_api()
+{
+  auto response = invoke_rpc(ShowRegisterHandlersRequest{});
+  _printer->write_output(response);
+}
+
+void
+DirectRPCCommand::read_from_input()
+{
+  // TODO: remove all the output messages from here if possible
+  std::string text;
+
+  try {
+    _printer->write_output(">> Ctrl-D to fire the request. Ctrl-C to exit\n");
+    std::cin >> std::noskipws;
+    // read cin.
+    std::string content((std::istreambuf_iterator<char>(std::cin)), std::istreambuf_iterator<char>());
+    if (!get_parsed_arguments()->get(RAW_STR) && !validate_input(content)) {
+      _printer->write_output(ts::bwprint(text, "Content not accepted. expecting a valid sequence or structure\n"));
+      return;
+    }
+    std::string const &response = invoke_rpc(content);
+    _printer->write_output("--> Request sent.\n");
+    _printer->write_output(ts::bwprint(text, "\n<-- {}\n", response));
+  } catch (std::exception const &ex) {
+    _printer->write_output(ts::bwprint(text, "Error found: {}\n", ex.what()));
+  }
+}
+
+void
+DirectRPCCommand::invoke_method()
+{
+  shared::rpc::ClientRequest request;
+  if (auto method = get_parsed_arguments()->get(INVOKE_STR); method) {
+    request.method = method.value();
+    // We build up the parameter content if passed.
+    if (auto params = get_parsed_arguments()->get(PARAMS_STR); params) {
+      std::ostringstream ss;
+      for (auto &&param : params) {
+        ss << param;
+        ss << '\n';
+      }
+      request.params = YAML::Load(ss.str()); // let if fail if this is bad.
+    }
+    _printer->write_output(invoke_rpc(request));
+  }
+}
+
+//------------------------------------------------------------------------------------------------------------------------------------
+ServerCommand::ServerCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  BasePrinter::Options printOpts{parse_print_opts(args)};
+  if (get_parsed_arguments()->get(DRAIN_STR)) {
+    _printer      = std::make_unique<GenericPrinter>(printOpts);
+    _invoked_func = [&]() { server_drain(); };
+  }
+}
+
+void
+ServerCommand::server_drain()
+{
+  shared::rpc::JSONRPCResponse response;
+  // TODO, can call_request take a && ?? if needed in the cmd just pass by ref.
+
+  if (get_parsed_arguments()->get(UNDO_STR)) {
+    response = invoke_rpc(ServerStopDrainRequest{});
+  } else {
+    const bool newConn = get_parsed_arguments()->get(NO_NEW_CONN_STR);
+    ServerStartDrainRequest request{{newConn}};
+    response = invoke_rpc(request);
+  }
+
+  _printer->write_output(response);
+}
+// //------------------------------------------------------------------------------------------------------------------------------------
+StorageCommand::StorageCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  BasePrinter::Options printOpts{parse_print_opts(args)};
+  if (get_parsed_arguments()->get(STATUS_STR)) {
+    _printer      = std::make_unique<CacheDiskStoragePrinter>(printOpts);
+    _invoked_func = [&]() { get_storage_status(); };
+  } else if (get_parsed_arguments()->get(OFFLINE_STR)) {
+    _printer      = std::make_unique<CacheDiskStorageOfflinePrinter>(printOpts);
+    _invoked_func = [&]() { set_storage_offline(); };
+  }
+}
+
+void
+StorageCommand::get_storage_status()
+{
+  auto disks = get_parsed_arguments()->get(STATUS_STR);
+  GetStorageDeviceStatusRequest request{{{std::begin(disks), std::end(disks)}}};
+  auto response = invoke_rpc(request);
+  _printer->write_output(response);
+}
+void
+StorageCommand::set_storage_offline()
+{
+  auto disks = get_parsed_arguments()->get(OFFLINE_STR);
+  SetStorageDeviceOfflineRequest request{{{std::begin(disks), std::end(disks)}}};
+  auto response = invoke_rpc(request);
+  _printer->write_output(response);
+}
+//------------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/traffic_ctl/CtrlCommands.h b/src/traffic_ctl/CtrlCommands.h
new file mode 100644
index 0000000..3fc9a2d
--- /dev/null
+++ b/src/traffic_ctl/CtrlCommands.h
@@ -0,0 +1,221 @@
+/**
+@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.
+*/
+#pragma once
+
+#include <iostream>
+
+#include "tscore/ArgParser.h"
+
+#include "shared/rpc/RPCClient.h"
+#include "CtrlPrinters.h"
+
+// ----------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief Base Control Command class.
+/// This class should be used as a base class for every new command or group of commands that are related.
+/// The base class will provide the client communication through the @c invoke_call member function. Arguments that were
+/// parsed by the traffic_ctl are available as a member to all the derived classes.
+class CtrlCommand
+{
+public:
+  virtual ~CtrlCommand() = default;
+
+  /// @brief This object will hold the arguments for now.
+  /// @note If you don't need to handle the args in your derived class you should just inherit CtrlCommand ctor:
+  ///       'using CtrlCommand::CtrlCommand;'
+  CtrlCommand(ts::Arguments *args);
+
+  /// @brief Main execution point for a particular command. This function will invoke @c _invoked_func which should be set
+  ///        by the derived class. In case you do not want the @c _invoked_func to be called directly, you should override this
+  ///        member function and call it yourself.
+  ///        If @c _invoked_func is not properly set, the function will throw @c logic_error
+  virtual void execute();
+
+protected:
+  /// @brief Invoke the remote server. This is the very basic function which does not play or interact with any codec. Request
+  ///        and message should be already en|de coded.
+  /// @param request A string representation of the json/yaml request.
+  /// @return a string with the json/yaml response.
+  /// @note This function does print the raw string if requested by the "--format". No printer involved, standard output.
+  std::string invoke_rpc(std::string const &request);
+
+  /// @brief Function that calls the rpc server. This function takes a json objects and uses the defined coded to convert them to a
+  ///        string. This function will call invoke_rpc(string) overload.
+  /// @param A Client request.
+  /// @return A server response.
+  shared::rpc::JSONRPCResponse invoke_rpc(shared::rpc::ClientRequest const &request);
+
+  /// @brief Function that calls the rpc server. The response will not be decoded, it will be a raw string.
+  void invoke_rpc(shared::rpc::ClientRequest const &request, std::string &bw);
+
+  std::unique_ptr<BasePrinter> _printer; //!< Specific output formatter. This should be created by the derived class.
+
+  /// @brief The whole design is that the command will execute the @c _invoked_func once invoked. This function ptr should be
+  ///        set by the appropriated derived class base on the passed parameters. The derived class have the option to override
+  ///        the execute() function and do something else. Check @c RecordCommand as an example.
+  std::function<void(void)> _invoked_func; //!< Actual function that the command will execute.
+
+  /// @brief Return the parsed arguments.
+  ts::Arguments *
+  get_parsed_arguments()
+  {
+    return _arguments;
+  }
+
+private:
+  ts::Arguments *_arguments = nullptr; //!< parsed traffic_ctl arguments.
+  shared::rpc::RPCClient _rpcClient;   //!< RPC socket client implementation.
+};
+
+// -----------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief Record Command Implementation
+///        Used as base class for any command that needs to access to a TS record.
+///        If deriving from this class, make sure you implement @c execute_subcommand() and call the _invoked_func yourself.
+class RecordCommand : public CtrlCommand
+{
+public:
+  using CtrlCommand::CtrlCommand;
+  virtual ~RecordCommand() = default;
+
+protected:
+  static inline const std::string MATCH_STR{"match"};
+  static inline const std::string GET_STR{"get"};
+  static inline const std::string DESCRIBE_STR{"describe"};
+
+  /// @brief Handy enum to hold which kind of records we are requesting.
+  enum class RecordQueryType { CONFIG = 0, METRIC };
+  /// @brief Function to fetch record from the rpc server.
+  /// @param argData argument's data.
+  /// @param isRegex if the request should be done by regex or name.
+  /// @param recQueryType Config or Metric.
+  shared::rpc::JSONRPCResponse record_fetch(ts::ArgumentData argData, bool isRegex, RecordQueryType recQueryType);
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
+class ConfigCommand : public RecordCommand
+{
+  static inline const std::string DIFF_STR{"diff"};
+  static inline const std::string DEFAULTS_STR{"defaults"};
+  static inline const std::string SET_STR{"set"};
+  static inline const std::string STATUS_STR{"status"};
+  static inline const std::string RELOAD_STR{"reload"};
+  static inline const std::string REGISTRY_STR{"registry"};
+
+  void config_match();
+  void config_get();
+  void config_describe();
+  void config_defaults();
+  void config_diff();
+  void config_status();
+  void config_set();
+  void config_reload();
+  void config_show_file_registry();
+
+public:
+  ConfigCommand(ts::Arguments *args);
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
+class MetricCommand : public RecordCommand
+{
+  static inline const std::string CLEAR_STR{"clear"};
+  static inline const std::string ZERO_STR{"zero"};
+
+  void metric_get();
+  void metric_match();
+  void metric_describe();
+  void metric_clear();
+  void metric_zero();
+
+public:
+  MetricCommand(ts::Arguments *args);
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
+class HostCommand : public CtrlCommand
+{
+public:
+  HostCommand(ts::Arguments *args);
+
+private:
+  static inline const std::string STATUS_STR{"status"};
+  static inline const std::string DOWN_STR{"down"};
+  static inline const std::string UP_STR{"up"};
+  static inline const std::string REASON_STR{"reason"};
+
+  void status_get();
+  void status_down();
+  void status_up();
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
+class PluginCommand : public CtrlCommand
+{
+public:
+  PluginCommand(ts::Arguments *args);
+
+private:
+  static inline const std::string MSG_STR{"msg"};
+  void plugin_msg();
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
+class DirectRPCCommand : public CtrlCommand
+{
+public:
+  DirectRPCCommand(ts::Arguments *args);
+
+private:
+  static inline const std::string GET_API_STR{"get-api"};
+  static inline const std::string FILE_STR{"file"};
+  static inline const std::string INPUT_STR{"input"};
+  static inline const std::string INVOKE_STR{"invoke"};
+  static inline const std::string RAW_STR{"raw"};
+  static inline const std::string PARAMS_STR{"params"};
+
+  void from_file_request();
+  void get_rpc_api();
+  void read_from_input();
+  void invoke_method();
+  /// run a YAML validation on the input.
+  bool validate_input(std::string const &in) const;
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
+class ServerCommand : public CtrlCommand
+{
+public:
+  ServerCommand(ts::Arguments *args);
+
+private:
+  static inline const std::string DRAIN_STR{"drain"};
+  static inline const std::string UNDO_STR{"undo"};
+  static inline const std::string NO_NEW_CONN_STR{"no-new-connection"};
+
+  void server_drain();
+};
+//
+// -----------------------------------------------------------------------------------------------------------------------------------
+struct StorageCommand : public CtrlCommand {
+  StorageCommand(ts::Arguments *args);
+
+private:
+  static inline const std::string STATUS_STR{"status"};
+  static inline const std::string OFFLINE_STR{"offline"};
+
+  void get_storage_status();
+  void set_storage_offline();
+};
+// -----------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/traffic_ctl/CtrlPrinters.cc b/src/traffic_ctl/CtrlPrinters.cc
new file mode 100644
index 0000000..aeaf203
--- /dev/null
+++ b/src/traffic_ctl/CtrlPrinters.cc
@@ -0,0 +1,372 @@
+/**
+  @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 "CtrlPrinters.h"
+
+#include <iostream>
+#include <unordered_map>
+#include <string_view>
+
+#include "jsonrpc/ctrl_yaml_codecs.h"
+#include "tscpp/util/ts_meta.h"
+#include <tscore/BufferWriter.h>
+#include "PrintUtils.h"
+
+//------------------------------------------------------------------------------------------------------------------------------------
+
+namespace
+{
+void
+print_record_error_list(std::vector<shared::rpc::RecordLookUpResponse::RecordError> const &errors)
+{
+  if (errors.size()) {
+    std::cout << "------------ Errors ----------\n";
+    auto iter = std::begin(errors);
+    if (iter != std::end(errors)) {
+      std::cout << *iter;
+    }
+    ++iter;
+    for (auto err = iter; err != std::end(errors); ++err) {
+      std::cout << "--\n";
+      std::cout << *err;
+    }
+  }
+}
+
+} // namespace
+void
+BasePrinter::write_output(shared::rpc::JSONRPCResponse const &response)
+{
+  // If json, then we print the full message, either ok or error.
+  if (this->is_json_format()) {
+    write_output_json(response.fullMsg);
+    return;
+  }
+
+  if (response.is_error()) {
+    // If an error is present, then as per the specs we can ignore the jsonrpc.result field, so we print the error and we are done
+    // here!
+    std::cout << response.error.as<shared::rpc::JSONRPCError>(); // Already formatted.
+    return;
+  }
+
+  if (!response.result.IsNull()) {
+    // on you!
+    // Found convinient to let the derived class deal with the specifics.
+    write_output(response.result);
+  }
+}
+
+void
+BasePrinter::write_output(std::string_view output) const
+{
+  std::cout << output << '\n';
+}
+
+void
+BasePrinter::write_debug(std::string_view output) const
+{
+  std::cout << output << '\n';
+}
+void
+BasePrinter::write_output_json(YAML::Node const &node) const
+{
+  YAML::Emitter out;
+  out << YAML::DoubleQuoted << YAML::Flow;
+  out << node;
+  write_output(std::string_view{out.c_str()});
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+RecordPrinter::write_output(YAML::Node const &result)
+{
+  auto response = result.as<shared::rpc::RecordLookUpResponse>();
+  if (is_legacy_format()) {
+    write_output_legacy(response);
+  } else {
+    write_output_pretty(response);
+  }
+}
+void
+RecordPrinter::write_output_legacy(shared::rpc::RecordLookUpResponse const &response)
+{
+  std::string text;
+  for (auto &&recordInfo : response.recordList) {
+    if (!recordInfo.registered) {
+      std::cout << recordInfo.name
+                << ": Unrecognized configuration value. Record is a configuration name/value but is not registered\n";
+      continue;
+    }
+    if (!_printAsRecords) {
+      std::cout << recordInfo.name << ": " << recordInfo.currentValue << '\n';
+    } else {
+      std::cout << ts::bwprint(text, "{} {} {} {} # default: {}\n", rec_labelof(recordInfo.rclass), recordInfo.name,
+                               recordInfo.dataType, recordInfo.currentValue, recordInfo.defaultValue);
+    }
+  }
+  // we print errors if found.
+  print_record_error_list(response.errorList);
+}
+void
+RecordPrinter::write_output_pretty(shared::rpc::RecordLookUpResponse const &response)
+{
+  write_output_legacy(response);
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+MetricRecordPrinter::write_output(YAML::Node const &result)
+{
+  auto response = result.as<shared::rpc::RecordLookUpResponse>();
+  for (auto &&recordInfo : response.recordList) {
+    std::cout << recordInfo.name << " " << recordInfo.currentValue << '\n';
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+
+void
+DiffConfigPrinter::write_output(YAML::Node const &result)
+{
+  std::string text;
+  auto response = result.as<shared::rpc::RecordLookUpResponse>();
+  for (auto &&recordInfo : response.recordList) {
+    auto const &currentValue = recordInfo.currentValue;
+    auto const &defaultValue = recordInfo.defaultValue;
+    const bool hasChanged    = (currentValue != defaultValue);
+    if (hasChanged) {
+      if (!_printAsRecords) {
+        std::cout << ts::bwprint(text, "{} has changed\n", recordInfo.name);
+        std::cout << ts::bwprint(text, "\tCurrent Value: {}\n", currentValue);
+        std::cout << ts::bwprint(text, "\tDefault Value: {}\n", defaultValue);
+      } else {
+        std::cout << ts::bwprint(text, "{} {} {} {} # default: {}\n", rec_labelof(recordInfo.rclass), recordInfo.name,
+                                 recordInfo.dataType, recordInfo.currentValue, recordInfo.defaultValue);
+      }
+    }
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+ConfigReloadPrinter::write_output(YAML::Node const &result)
+{
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+ConfigShowFileRegistryPrinter::write_output(YAML::Node const &result)
+{
+  if (is_pretty_format()) {
+    this->write_output_pretty(result);
+  } else {
+    if (auto registry = result["config_registry"]) {
+      write_output_json(registry);
+    }
+  }
+}
+
+void
+ConfigShowFileRegistryPrinter::write_output_pretty(YAML::Node const &result)
+{
+  if (auto &&registry = result["config_registry"]) {
+    for (auto &&element : registry) {
+      std::cout << "┌ " << element["file_path"] << '\n';
+      std::cout << "└┬ Config name: " << element["config_record_name"] << '\n';
+      std::cout << " ├ Parent config: " << element["parent_config"] << '\n';
+      std::cout << " ├ Root access needed: " << element["root_access_needed"] << '\n';
+      std::cout << " └ Is required: " << element["is_required"] << '\n';
+    }
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+ConfigSetPrinter::write_output(YAML::Node const &result)
+{
+  // we match the legacy format, the only one supported for now.
+  static const std::unordered_map<std::string, std::string> Update_Type_To_String_Message = {
+    {"0", "Set {}"},                                                                                           // UNDEFINED
+    {"1", "Set {}, please wait 10 seconds for traffic server to sync configuration, restart is not required"}, // DYNAMIC
+    {"2", "Set {}, restart required"},                                                                         // RESTART_TS
+    {"3", "Set {}, restart required"} // RESTART TM, we take care of this in case we get it from TS.
+  };
+  std::string text;
+  try {
+    auto const &response = result.as<ConfigSetRecordResponse>();
+    for (auto &&updatedRec : response.data) {
+      if (auto search = Update_Type_To_String_Message.find(updatedRec.updateType);
+          search != std::end(Update_Type_To_String_Message)) {
+        std::cout << ts::bwprint(text, search->second, updatedRec.recName) << '\n';
+      } else {
+        std::cout << "Oops we don't know how to handle the update status for '" << updatedRec.recName << "' ["
+                  << updatedRec.updateType << "]\n";
+      }
+    }
+  } catch (std::exception const &ex) {
+    std::cout << ts::bwprint(text, "Unexpected error found {}", ex.what());
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+RecordDescribePrinter::write_output(YAML::Node const &result)
+{
+  auto const &response = result.as<shared::rpc::RecordLookUpResponse>();
+  if (is_legacy_format()) {
+    write_output_legacy(response);
+  } else {
+    write_output_pretty(response);
+  }
+}
+
+void
+RecordDescribePrinter::write_output_legacy(shared::rpc::RecordLookUpResponse const &response)
+{
+  std::string text;
+  for (auto &&recordInfo : response.recordList) {
+    if (!recordInfo.registered) {
+      std::cout << recordInfo.name
+                << ": Unrecognized configuration value. Record is a configuration name/value but is not registered\n";
+      continue;
+    }
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Name", recordInfo.name);
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Current Value ", recordInfo.currentValue);
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Default Value ", recordInfo.defaultValue);
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Record Type ", rec_labelof(recordInfo.rclass));
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Data Type ", recordInfo.dataType);
+
+    std::visit(ts::meta::overloaded{
+                 [&](shared::rpc::RecordLookUpResponse::RecordParamInfo::ConfigMeta const &meta) {
+                   std::cout << ts::bwprint(text, "{:16s}: {}\n", "Access Control ", rec_accessof(meta.accessType));
+                   std::cout << ts::bwprint(text, "{:16s}: {}\n", "Update Type ", rec_updateof(meta.updateType));
+                   std::cout << ts::bwprint(text, "{:16s}: {}\n", "Update Status ", meta.updateStatus);
+                   std::cout << ts::bwprint(text, "{:16s}: {}\n", "Source ", rec_sourceof(meta.source));
+
+                   std::cout << ts::bwprint(text, "{:16s}: {}\n", "Syntax Check ", meta.checkExpr);
+                 },
+                 [&](shared::rpc::RecordLookUpResponse::RecordParamInfo::StatMeta const &meta) {
+                   // This may not be what we want, as for a metric we may not need to print all the same info. In that case
+                   // just create a new printer for this.
+                   std::cout << ts::bwprint(text, "{:16s}: {}\n", "Persist Type ", meta.persistType);
+                 },
+               },
+               recordInfo.meta);
+
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Overridable", (recordInfo.overridable ? "yes" : "no"));
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Version ", recordInfo.version);
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Order ", recordInfo.order);
+    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Raw Stat Block ", recordInfo.rsb);
+  }
+
+  // also print errors.
+  print_record_error_list(response.errorList);
+}
+
+void
+RecordDescribePrinter::write_output_pretty(shared::rpc::RecordLookUpResponse const &response)
+{
+  // we default for legacy.
+  write_output_legacy(response);
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+GetHostStatusPrinter::write_output(YAML::Node const &result)
+{
+  auto resp = result.as<HostStatusLookUpResponse>();
+
+  if (resp.statusList.size() > 0) {
+    for (auto &&host : resp.statusList) {
+      std::cout << host.hostName << " " << host.status << '\n';
+    }
+    std::cout << '\n';
+  }
+
+  for (auto &&e : resp.errorList) {
+    std::cout << e << '\n';
+  }
+}
+
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+SetHostStatusPrinter::write_output(YAML::Node const &result)
+{
+  // do nothing.
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+
+void
+CacheDiskStoragePrinter::write_output(YAML::Node const &result)
+{
+  // do nothing.
+  if (!is_legacy_format()) {
+    write_output_pretty(result);
+  }
+}
+void
+CacheDiskStoragePrinter::write_output_pretty(YAML::Node const &result)
+{
+  auto my_print = [](auto const &disk) {
+    std::cout << "Device: " << disk.path << '\n';
+    std::cout << "Status: " << disk.status << '\n';
+    std::cout << "Error Count: " << disk.errorCount << '\n';
+  };
+
+  auto const &resp = result.as<DeviceStatusInfoResponse>();
+  auto iter        = std::begin(resp.data);
+  my_print(*iter);
+  ++iter;
+  for (; iter != std::end(resp.data); ++iter) {
+    std::cout << "---\n";
+    my_print(*iter);
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+CacheDiskStorageOfflinePrinter::write_output(YAML::Node const &result)
+{
+  if (!is_legacy_format()) {
+    write_output_pretty(result);
+  }
+}
+void
+CacheDiskStorageOfflinePrinter::write_output_pretty(YAML::Node const &result)
+{
+  for (auto &&item : result) {
+    if (auto n = item["has_online_storage_left"]) {
+      bool any_left = n.as<bool>();
+      if (!any_left) {
+        std::cout << "No more online storage left" << helper::try_extract<std::string>(n, "path") << '\n';
+      }
+    }
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+void
+RPCAPIPrinter::write_output(YAML::Node const &result)
+{
+  if (auto methods = result["methods"]) {
+    std::cout << "Methods:\n";
+    for (auto &&m : methods) {
+      std::cout << "- " << m.as<std::string>() << '\n';
+    }
+  }
+  if (auto notifications = result["notifications"]) {
+    std::cout << "Notifications:\n";
+    for (auto &&m : notifications) {
+      std::cout << "- " << m.as<std::string>() << '\n';
+    }
+  }
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/traffic_ctl/CtrlPrinters.h b/src/traffic_ctl/CtrlPrinters.h
new file mode 100644
index 0000000..c641f5b
--- /dev/null
+++ b/src/traffic_ctl/CtrlPrinters.h
@@ -0,0 +1,248 @@
+/** @file
+
+  @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.
+ */
+#pragma once
+
+#include <iostream>
+#include <yaml-cpp/yaml.h>
+
+#include "shared/rpc/RPCRequests.h"
+
+//------------------------------------------------------------------------------------------------------------------------------------
+///
+/// Base class that implements the basic output format.
+///
+/// Every command will print out specific details depending on the nature of the message. This base class models the basic API.
+/// @c _format member should be set when the object is created, this member should be used to  decide the way we want to generate
+/// the output and possibly(TODO) where we want it(stdout, stderr, etc.). If no output is needed GenericPrinter can be used which is
+/// muted.
+class BasePrinter
+{
+public:
+  /// This enum maps the --format flag coming from traffic_ctl. (also --records is included here, see comments down below.)
+  struct Options {
+    enum class OutputFormat {
+      LEGACY = 0, // Legacy format, mimics the old traffic_ctl output
+      PRETTY,     // Enhanced printing messages. (in case you would like to generate them)
+      JSON,       // Json formatting
+      RECORDS,    // only valid for configs, but it's handy to have it here.
+      RPC         // Print JSONRPC request and response + default output.
+    };
+    Options() = default;
+    Options(OutputFormat fmt) : _format(fmt) {}
+    OutputFormat _format{OutputFormat::LEGACY}; //!< selected(passed) format.
+  };
+
+  /// Printer constructor. Needs the format as it will be used by derived classes.
+  BasePrinter(Options opts) : _printOpt(opts) {}
+
+  BasePrinter()          = default;
+  virtual ~BasePrinter() = default;
+
+  ///
+  /// Function that will generate the expected output based on the response result.
+  ///
+  /// If the response contains any high level error, it will be print and the the specific derived class @c write_output() will not
+  /// be called.
+  /// @param response the  server response.
+  ///
+  void write_output(shared::rpc::JSONRPCResponse const &response);
+
+  ///
+  /// Write output based on the response values.
+  ///
+  /// Implement this one so you deal with the expected output, @c _format will be already set to the right
+  /// selected type so you can decide what to print.
+  ///
+  /// @param result jsonrpc result structure. No format specified by us, it's the one specified by the actual jsonrpc
+  ///               response.
+  ///
+  virtual void write_output(YAML::Node const &result) = 0;
+
+  virtual void write_output(std::string_view output) const;
+  virtual void write_debug(std::string_view output) const;
+
+  /// OutputFormat getters.
+  Options::OutputFormat get_format() const;
+  bool print_rpc_message() const;
+  bool is_json_format() const;
+  bool is_legacy_format() const;
+  bool is_records_format() const;
+  bool is_pretty_format() const;
+
+protected:
+  void write_output_json(YAML::Node const &node) const;
+  Options _printOpt;
+};
+
+inline BasePrinter::Options::OutputFormat
+BasePrinter::get_format() const
+{
+  return _printOpt._format;
+}
+
+inline bool
+BasePrinter::print_rpc_message() const
+{
+  return get_format() == Options::OutputFormat::RPC;
+}
+
+inline bool
+BasePrinter::is_json_format() const
+{
+  return get_format() == Options::OutputFormat::JSON;
+}
+
+inline bool
+BasePrinter::is_legacy_format() const
+{
+  return get_format() == Options::OutputFormat::LEGACY;
+}
+inline bool
+BasePrinter::is_records_format() const
+{
+  return get_format() == Options::OutputFormat::RECORDS;
+}
+inline bool
+BasePrinter::is_pretty_format() const
+{
+  return get_format() == Options::OutputFormat::PRETTY;
+}
+//------------------------------------------------------------------------------------------------------------------------------------
+class GenericPrinter : public BasePrinter
+{
+  void
+  write_output(YAML::Node const &result) override
+  {
+    /* muted */
+  }
+
+public:
+  GenericPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class RecordPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+  void write_output_legacy(shared::rpc::RecordLookUpResponse const &result);
+  void write_output_pretty(shared::rpc::RecordLookUpResponse const &result);
+
+public:
+  RecordPrinter(Options opt) : BasePrinter(opt) { _printAsRecords = is_records_format(); }
+
+protected:
+  bool _printAsRecords{false};
+};
+
+class MetricRecordPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+
+public:
+  MetricRecordPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class DiffConfigPrinter : public RecordPrinter
+{
+  void write_output(YAML::Node const &result) override;
+  void write_output_pretty(YAML::Node const &result);
+
+public:
+  DiffConfigPrinter(BasePrinter::Options opt) : RecordPrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class ConfigReloadPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+  void write_output_pretty(YAML::Node const &result);
+
+public:
+  ConfigReloadPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class ConfigShowFileRegistryPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+  void write_output_pretty(YAML::Node const &result);
+
+public:
+  using BasePrinter::BasePrinter;
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class ConfigSetPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+
+public:
+  using BasePrinter::BasePrinter;
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class RecordDescribePrinter : public BasePrinter
+{
+  void write_output_legacy(shared::rpc::RecordLookUpResponse const &result);
+  void write_output_pretty(shared::rpc::RecordLookUpResponse const &result);
+  void write_output(YAML::Node const &result) override;
+
+public:
+  RecordDescribePrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class GetHostStatusPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+
+public:
+  GetHostStatusPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class SetHostStatusPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+
+public:
+  SetHostStatusPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class CacheDiskStoragePrinter : public BasePrinter
+{
+  void write_output_pretty(YAML::Node const &result);
+  void write_output(YAML::Node const &result) override;
+
+public:
+  CacheDiskStoragePrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class CacheDiskStorageOfflinePrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+  void write_output_pretty(YAML::Node const &result);
+
+public:
+  CacheDiskStorageOfflinePrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+class RPCAPIPrinter : public BasePrinter
+{
+  void write_output(YAML::Node const &result) override;
+
+public:
+  RPCAPIPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
+};
+//------------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/traffic_ctl/Makefile.inc b/src/traffic_ctl/Makefile.inc
index a0083de..4cc365e 100644
--- a/src/traffic_ctl/Makefile.inc
+++ b/src/traffic_ctl/Makefile.inc
@@ -20,34 +20,18 @@
 bin_PROGRAMS += traffic_ctl/traffic_ctl
 
 traffic_ctl_traffic_ctl_CPPFLAGS = \
-    $(AM_CPPFLAGS) \
-    $(iocore_include_dirs) \
-    -I$(abs_top_srcdir)/include \
-    -I$(abs_top_srcdir)/proxy/http \
-    -I$(abs_top_srcdir)/proxy/hdrs \
-    -I$(abs_top_srcdir)/lib \
-    -I$(abs_top_srcdir)/mgmt \
-    -I$(abs_top_srcdir)/mgmt/api/include \
-    -I$(abs_top_srcdir)/proxy \
-    $(TS_INCLUDES)
+	$(AM_CPPFLAGS) \
+	$(TS_INCLUDES) \
+	-I$(abs_top_srcdir)/mgmt \
+	-I$(abs_top_srcdir)/include \
+	@SWOC_INCLUDES@ @YAMLCPP_INCLUDES@
 
 traffic_ctl_traffic_ctl_SOURCES = \
-	traffic_ctl/alarm.cc \
-	traffic_ctl/config.cc \
-	traffic_ctl/metric.cc \
-	traffic_ctl/plugin.cc \
-	traffic_ctl/server.cc \
-	traffic_ctl/storage.cc \
-	traffic_ctl/host.cc \
-	shared/overridable_txn_vars.cc \
-	traffic_ctl/traffic_ctl.cc
+	traffic_ctl/traffic_ctl.cc \
+	traffic_ctl/CtrlPrinters.cc \
+	traffic_ctl/CtrlCommands.cc \
+	shared/rpc/IPCSocketClient.cc
 
 traffic_ctl_traffic_ctl_LDADD = \
-	$(top_builddir)/src/records/librecords_p.a \
-	$(top_builddir)/mgmt/libmgmt_p.la \
-	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
-	$(top_builddir)/iocore/eventsystem/libinkevent.a \
-	$(top_builddir)/mgmt/api/libtsmgmt.la \
-	$(top_builddir)/src/tscore/libtscore.la \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	@HWLOC_LIBS@
+    $(top_builddir)/src/tscore/libtscore.la \
+    @HWLOC_LIBS@ @SWOC_LIBS@ @YAMLCPP_LIBS@
diff --git a/src/traffic_ctl/PrintUtils.h b/src/traffic_ctl/PrintUtils.h
new file mode 100644
index 0000000..44ab34e
--- /dev/null
+++ b/src/traffic_ctl/PrintUtils.h
@@ -0,0 +1,94 @@
+/**
+  @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.
+*/
+#pragma once
+
+// Record access control, indexed by RecAccessT.
+static const char *
+rec_accessof(int rec_access)
+{
+  switch (rec_access) {
+  case 1:
+    return "no access";
+  case 2:
+    return "read only";
+  case 0: /* fallthrough */
+  default:
+    return "default";
+  }
+}
+static const char *
+rec_updateof(int rec_updatetype)
+{
+  switch (rec_updatetype) {
+  case 1:
+    return "dynamic, no restart";
+  case 2:
+    return "static, restart traffic_server";
+  case 3:
+    return "Oops, we shouldn't be using this update type";
+  case 0: /* fallthrough */
+  default:
+    return "none";
+  }
+}
+
+[[maybe_unused]] static const char *
+rec_checkof(int rec_checktype)
+{
+  switch (rec_checktype) {
+  case 1:
+    return "string matching a regular expression";
+  case 2:
+    return "integer with a specified range";
+  case 3:
+    return "IP address";
+  case 0: /* fallthrough */
+  default:
+    return "none";
+  }
+}
+static const char *
+rec_labelof(int rec_class)
+{
+  switch (rec_class) {
+  case 1:
+    return "CONFIG";
+  case 16:
+    return "LOCAL";
+  default:
+    return "unknown";
+  }
+}
+static const char *
+rec_sourceof(int rec_source)
+{
+  switch (rec_source) {
+  case 1:
+    return "built in default";
+  case 3:
+    return "administratively set";
+  case 2:
+    return "plugin default";
+  case 4:
+    return "environment";
+  default:
+    return "unknown";
+  }
+}
\ No newline at end of file
diff --git a/src/traffic_ctl/alarm.cc b/src/traffic_ctl/alarm.cc
deleted file mode 100644
index 32821ff..0000000
--- a/src/traffic_ctl/alarm.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-/** @file
-
-  traffic_ctl
-
-  @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 "traffic_ctl.h"
-
-struct AlarmListPolicy {
-  using entry_type = char *;
-
-  static void
-  free(entry_type e)
-  {
-    TSfree(e);
-  }
-
-  static entry_type
-  cast(void *ptr)
-  {
-    return static_cast<entry_type>(ptr);
-  }
-};
-
-using CtrlAlarmList = CtrlMgmtList<AlarmListPolicy>;
-
-void
-CtrlEngine::alarm_list()
-{
-  TSMgmtError error;
-  CtrlAlarmList alarms;
-
-  error = TSActiveEventGetMlt(alarms.list);
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to fetch active alarms");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  while (!alarms.empty()) {
-    char *a = alarms.next();
-    std::cout << a << std::endl;
-    TSfree(a);
-  }
-}
-
-void
-CtrlEngine::alarm_clear()
-{
-  TSMgmtError error;
-  CtrlAlarmList alarms;
-
-  // First get the active alarms ...
-  error = TSActiveEventGetMlt(alarms.list);
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to fetch active alarms");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  // Now resolve them all ...
-  while (!alarms.empty()) {
-    char *a = alarms.next();
-
-    error = TSEventResolve(a);
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to resolve %s", a);
-      TSfree(a);
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    TSfree(a);
-  }
-}
-
-void
-CtrlEngine::alarm_resolve()
-{
-  TSMgmtError error;
-  CtrlAlarmList alarms;
-
-  for (const auto &it : arguments.get("resolve")) {
-    error = TSEventResolve(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to resolve %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-  }
-}
diff --git a/src/traffic_ctl/config.cc b/src/traffic_ctl/config.cc
deleted file mode 100644
index e343339..0000000
--- a/src/traffic_ctl/config.cc
+++ /dev/null
@@ -1,463 +0,0 @@
-/** @file
-
-  traffic_ctl
-
-  @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 "traffic_ctl.h"
-#include <ctime>
-#include "records/I_RecDefs.h"
-#include "records/P_RecUtils.h"
-#include "ts/apidefs.h"
-#include "HTTP.h"
-#include "HttpConnectionCount.h"
-#include "shared/overridable_txn_vars.h"
-#include <tscore/BufferWriter.h>
-
-struct RecordDescriptionPolicy {
-  using entry_type = TSConfigRecordDescription *;
-
-  static void
-  free(entry_type e)
-  {
-    TSConfigRecordDescriptionDestroy(e);
-  }
-
-  static entry_type
-  cast(void *ptr)
-  {
-    return (entry_type)ptr;
-  }
-};
-
-struct CtrlMgmtRecordDescriptionList : CtrlMgmtList<RecordDescriptionPolicy> {
-  TSMgmtError
-  match(const char *regex)
-  {
-    return TSConfigRecordDescribeMatchMlt(regex, 0u /* flags */, this->list);
-  }
-};
-
-// Record data type names, indexed by TSRecordT.
-static const char *
-rec_typeof(int rec_type)
-{
-  switch (rec_type) {
-  case TS_REC_INT:
-    return "INT";
-  case TS_REC_COUNTER:
-    return "COUNTER";
-  case TS_REC_FLOAT:
-    return "FLOAT";
-  case TS_REC_STRING:
-    return "STRING";
-  case TS_REC_UNDEFINED: /* fallthrough */
-  default:
-    return "UNDEFINED";
-  }
-}
-
-// Record type name, indexed by RecT.
-static const char *
-rec_classof(int rec_class)
-{
-  switch (rec_class) {
-  case RECT_CONFIG:
-    return "standard config";
-  case RECT_LOCAL:
-    return "local config";
-  case RECT_PROCESS:
-    return "process metric";
-  case RECT_NODE:
-    return "node metric";
-  case RECT_PLUGIN:
-    return "plugin metric";
-  default:
-    return "undefined";
-  }
-}
-
-// Record access control, indexed by RecAccessT.
-static const char *
-rec_accessof(int rec_access)
-{
-  switch (rec_access) {
-  case RECA_NO_ACCESS:
-    return "no access";
-  case RECA_READ_ONLY:
-    return "read only";
-  case RECA_NULL: /* fallthrough */
-  default:
-    return "default";
-  }
-}
-
-// Record access control, indexed by RecUpdateT.
-static const char *
-rec_updateof(int rec_updatetype)
-{
-  switch (rec_updatetype) {
-  case RECU_DYNAMIC:
-    return "dynamic, no restart";
-  case RECU_RESTART_TS:
-    return "static, restart traffic_server";
-  case RECU_RESTART_TM:
-    return "static, restart traffic_manager";
-  case RECU_NULL: /* fallthrough */
-  default:
-    return "none";
-  }
-}
-
-// Record check type, indexed by RecCheckT.
-static const char *
-rec_checkof(int rec_checktype)
-{
-  switch (rec_checktype) {
-  case RECC_STR:
-    return "string matching a regular expression";
-  case RECC_INT:
-    return "integer with a specified range";
-  case RECC_IP:
-    return "IP address";
-  case RECC_NULL: /* fallthrough */
-  default:
-    return "none";
-  }
-}
-
-static const char *
-rec_sourceof(int rec_source)
-{
-  switch (rec_source) {
-  case REC_SOURCE_DEFAULT:
-    return "built in default";
-  case REC_SOURCE_EXPLICIT:
-    return "administratively set";
-  case REC_SOURCE_PLUGIN:
-    return "plugin default";
-  case REC_SOURCE_ENV:
-    return "environment";
-  default:
-    return "unknown";
-  }
-}
-
-static const char *
-rec_labelof(int rec_class)
-{
-  switch (rec_class) {
-  case RECT_CONFIG:
-    return "CONFIG";
-  case RECT_LOCAL:
-    return "LOCAL";
-  default:
-    return nullptr;
-  }
-}
-
-static const char *
-rec_datatypeof(TSRecordDataType dt)
-{
-  switch (dt) {
-  case TS_RECORDDATATYPE_INT:
-    return "int";
-  case TS_RECORDDATATYPE_NULL:
-    return "null";
-  case TS_RECORDDATATYPE_FLOAT:
-    return "float";
-  case TS_RECORDDATATYPE_STRING:
-    return "string";
-  case TS_RECORDDATATYPE_COUNTER:
-    return "counter";
-  case TS_RECORDDATATYPE_STAT_CONST:
-    return "constant stat";
-  case TS_RECORDDATATYPE_STAT_FX:
-    return "stat fx";
-  case TS_RECORDDATATYPE_MAX:
-    return "*";
-  }
-  return "?";
-}
-
-static std::string
-timestr(time_t tm)
-{
-  char buf[32];
-  return std::string(ctime_r(&tm, buf));
-}
-
-static void
-format_record(const CtrlMgmtRecord &record, bool recfmt)
-{
-  CtrlMgmtRecordValue value(record);
-
-  if (recfmt) {
-    std::cout << rec_labelof(record.rclass()) << ' ' << record.name() << ' ' << rec_typeof(record.type()) << ' ' << value.c_str()
-              << std::endl;
-  } else {
-    std::cout << record.name() << ": " << value.c_str() << std::endl;
-  }
-}
-
-void
-CtrlEngine::config_get()
-{
-  for (const auto &it : arguments.get("get")) {
-    CtrlMgmtRecord record;
-    TSMgmtError error;
-
-    error = record.fetch(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    if (REC_TYPE_IS_CONFIG(record.rclass())) {
-      format_record(record, arguments.get("records"));
-    }
-  }
-}
-
-void
-CtrlEngine::config_describe()
-{
-  for (const auto &it : arguments.get("describe")) {
-    TSConfigRecordDescription desc;
-    TSMgmtError error;
-
-    ink_zero(desc);
-    error = TSConfigRecordDescribe(it.c_str(), 0 /* flags */, &desc);
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to describe %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    auto ov_iter       = ts::Overridable_Txn_Vars.find(it);
-    bool overridable_p = (ov_iter != ts::Overridable_Txn_Vars.end());
-
-    std::string text;
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Name", desc.rec_name);
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Current Value ", CtrlMgmtRecordValue(desc.rec_type, desc.rec_value).c_str());
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Default Value ", CtrlMgmtRecordValue(desc.rec_type, desc.rec_default).c_str());
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Record Type ", rec_classof(desc.rec_class));
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Data Type ", rec_typeof(desc.rec_type));
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Access Control ", rec_accessof(desc.rec_access));
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Update Type ", rec_updateof(desc.rec_updatetype));
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Update Status ", desc.rec_update);
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Source ", rec_sourceof(desc.rec_source));
-    std::cout << ts::bwprint(text, "{:16s}: {} {}\n", "Overridable", overridable_p ? "yes" : "no",
-                             overridable_p ? rec_datatypeof(std::get<1>(ov_iter->second)) : "");
-
-    if (strlen(desc.rec_checkexpr)) {
-      std::cout << ts::bwprint(text, "{:16s}: {}\n", "Syntax Check ", rec_checkof(desc.rec_checktype), desc.rec_checkexpr);
-    } else {
-      std::cout << ts::bwprint(text, "{:16s}: {}\n", "Syntax Check ", rec_checkof(desc.rec_checktype));
-    }
-
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Version ", desc.rec_version);
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Order ", desc.rec_order);
-    std::cout << ts::bwprint(text, "{:16s}: {}\n", "Raw Stat Block ", desc.rec_rsb);
-
-    TSConfigRecordDescriptionFree(&desc);
-  }
-}
-
-void
-CtrlEngine::config_set()
-{
-  TSMgmtError error;
-  TSActionNeedT action;
-  auto set_data        = arguments.get("set");
-  const char *rec_name = set_data[0].c_str();
-  const char *rec_val  = set_data[1].c_str();
-  error                = TSRecordSet(rec_name, rec_val, &action);
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to set %s", rec_name);
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  switch (action) {
-  case TS_ACTION_SHUTDOWN:
-    std::cout << "set " << rec_name << ", full shutdown required" << std::endl;
-    break;
-  case TS_ACTION_RESTART:
-    std::cout << "set " << rec_name << ", restart required" << std::endl;
-    break;
-  case TS_ACTION_RECONFIGURE:
-    std::cout << "set " << rec_name << ", please wait 10 seconds for traffic server to sync configuration, restart is not required"
-              << std::endl;
-    break;
-  case TS_ACTION_DYNAMIC:
-  default:
-    printf("set %s\n", rec_name);
-    break;
-  }
-}
-
-void
-CtrlEngine::config_match()
-{
-  for (const auto &it : arguments.get("match")) {
-    CtrlMgmtRecordList reclist;
-    TSMgmtError error;
-
-    // XXX filter the results to only match configuration records.
-
-    error = reclist.match(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    while (!reclist.empty()) {
-      CtrlMgmtRecord record(reclist.next());
-      if (REC_TYPE_IS_CONFIG(record.rclass())) {
-        format_record(record, arguments.get("records"));
-      }
-    }
-  }
-}
-
-void
-CtrlEngine::config_reload()
-{
-  TSMgmtError error = TSReconfigure();
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "configuration reload request failed");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
-
-void
-CtrlEngine::config_status()
-{
-  CtrlMgmtRecord version;
-  CtrlMgmtRecord configtime;
-  CtrlMgmtRecord starttime;
-  CtrlMgmtRecord reconfig;
-  CtrlMgmtRecord proxy;
-  CtrlMgmtRecord manager;
-
-  CTRL_MGMT_CHECK(version.fetch("proxy.process.version.server.long"));
-  CTRL_MGMT_CHECK(starttime.fetch("proxy.node.restarts.proxy.start_time"));
-  CTRL_MGMT_CHECK(configtime.fetch("proxy.node.config.reconfigure_time"));
-  CTRL_MGMT_CHECK(reconfig.fetch("proxy.node.config.reconfigure_required"));
-  CTRL_MGMT_CHECK(proxy.fetch("proxy.node.config.restart_required.proxy"));
-  CTRL_MGMT_CHECK(manager.fetch("proxy.node.config.restart_required.manager"));
-
-  std::cout << CtrlMgmtRecordValue(version).c_str() << std::endl;
-  std::cout << "Started at " << timestr(static_cast<time_t>(starttime.as_int())).c_str();
-  std::cout << "Last reconfiguration at " << timestr(static_cast<time_t>(configtime.as_int())).c_str();
-  std::cout << (reconfig.as_int() ? "Reconfiguration required" : "Configuration is current") << std::endl;
-
-  if (proxy.as_int()) {
-    std::cout << "traffic_server requires restarting" << std::endl;
-  }
-  if (manager.as_int()) {
-    std::cout << "traffic_manager requires restarting\n" << std::endl;
-  }
-}
-
-void
-CtrlEngine::config_defaults()
-{
-  TSMgmtError error;
-  CtrlMgmtRecordDescriptionList descriptions;
-
-  error = descriptions.match(".*");
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to fetch record metadata");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  while (!descriptions.empty()) {
-    TSConfigRecordDescription *desc = descriptions.next();
-    CtrlMgmtRecordValue deflt(desc->rec_type, desc->rec_default);
-
-    if (arguments.get("records")) {
-      std::cout << rec_labelof(desc->rec_class) << ' ' << desc->rec_name << ' ' << rec_typeof(desc->rec_type) << ' '
-                << deflt.c_str() << std::endl;
-    } else {
-      std::cout << desc->rec_name << ": " << deflt.c_str() << std::endl;
-    }
-    TSConfigRecordDescriptionDestroy(desc);
-  }
-}
-
-void
-CtrlEngine::config_diff()
-{
-  TSMgmtError error;
-  CtrlMgmtRecordDescriptionList descriptions;
-
-  error = descriptions.match(".*");
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to fetch record metadata");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  while (!descriptions.empty()) {
-    TSConfigRecordDescription *desc;
-    bool changed = false;
-
-    desc = descriptions.next();
-
-    switch (desc->rec_type) {
-    case TS_REC_INT:
-      changed = (desc->rec_value.int_val != desc->rec_default.int_val);
-      break;
-    case TS_REC_COUNTER:
-      changed = (desc->rec_value.counter_val != desc->rec_default.counter_val);
-      break;
-    case TS_REC_FLOAT:
-      changed = (desc->rec_value.float_val != desc->rec_default.float_val);
-      break;
-    case TS_REC_STRING:
-      changed = (strcmp(desc->rec_value.string_val, desc->rec_default.string_val) != 0);
-      break;
-    default:
-      break;
-    }
-
-    if (changed) {
-      CtrlMgmtRecordValue current(desc->rec_type, desc->rec_value);
-      CtrlMgmtRecordValue deflt(desc->rec_type, desc->rec_default);
-
-      if (arguments.get("records")) {
-        std::cout << rec_labelof(desc->rec_class) << ' ' << desc->rec_name << ' ' << rec_typeof(desc->rec_type) << ' '
-                  << current.c_str() << " # default: " << deflt.c_str() << std::endl;
-      } else {
-        std::cout << desc->rec_name << " has changed" << std::endl;
-        std::cout << "\tCurrent Value: " << current.c_str() << std::endl;
-        std::cout << "\tDefault Value: " << deflt.c_str() << std::endl;
-      }
-    }
-
-    TSConfigRecordDescriptionDestroy(desc);
-  }
-}
diff --git a/src/traffic_ctl/host.cc b/src/traffic_ctl/host.cc
deleted file mode 100644
index 1834104..0000000
--- a/src/traffic_ctl/host.cc
+++ /dev/null
@@ -1,120 +0,0 @@
-/** @file
-
-  host.cc
-
-  @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 "traffic_ctl.h"
-#include "HostStatus.h"
-#include "records/P_RecUtils.h"
-
-void
-CtrlEngine::status_get()
-{
-  for (const auto &it : arguments.get("status")) {
-    CtrlMgmtRecord record;
-    TSMgmtError error;
-    std::string str = stat_prefix + it;
-
-    error = record.fetch(str.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    if (REC_TYPE_IS_STAT(record.rclass())) {
-      std::cout << record.name() << ' ' << CtrlMgmtRecordValue(record).c_str() << std::endl;
-    }
-  }
-}
-
-void
-CtrlEngine::status_down()
-{
-  const char *usage      = "traffic_ctl host down --reason 'active | local | manual' --time seconds host ....";
-  unsigned int down_time = 0;
-  std::string reason     = arguments.get("reason").value();
-  std::string down       = arguments.get("time").value();
-
-  // if reason is not set, set it to manual (default)
-  if (reason.empty()) {
-    reason = Reason::MANUAL;
-  }
-  if (!down.empty()) {
-    down_time = atoi(down.c_str());
-  }
-
-  if (!Reason::validReason(reason.c_str())) {
-    fprintf(stderr, "\nInvalid reason: '%s'\n\n", reason.c_str());
-    fprintf(stderr, "Usage: %s\n\n", usage);
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  TSMgmtError error = TS_ERR_OKAY;
-  for (const auto &it : arguments.get("down")) {
-    if (strncmp(it.c_str(), "--", 2) == 0) {
-      fprintf(stderr, "\nInvalid option: %s\n", it.c_str());
-      fprintf(stderr, "Usage: %s\n\n", usage);
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-    error = TSHostStatusSetDown(it.c_str(), down_time, reason.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to set %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-  }
-}
-void
-CtrlEngine::status_up()
-{
-  const char *usage  = "traffic_ctl host up --reason 'active | local | manual' host ....";
-  std::string reason = arguments.get("reason").value();
-
-  // if reason is not set, set it to manual (default)
-  if (reason.empty()) {
-    reason = Reason::MANUAL;
-  }
-
-  if (!Reason::validReason(reason.c_str())) {
-    fprintf(stderr, "\nInvalid reason: '%s'\n\n", reason.c_str());
-    fprintf(stderr, "Usage: %s\n\n", usage);
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  TSMgmtError error;
-  for (const auto &it : arguments.get("up")) {
-    error = TSHostStatusSetUp(it.c_str(), 0, reason.c_str());
-    if (strncmp("--", it.c_str(), 2) == 0) {
-      fprintf(stderr, "\nInvalid option: %s\n", it.c_str());
-      fprintf(stderr, "Usage: %s\n\n", usage);
-      status_code = CTRL_EX_ERROR;
-    }
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to set %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-  }
-}
diff --git a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
new file mode 100644
index 0000000..cc03492
--- /dev/null
+++ b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
@@ -0,0 +1,254 @@
+/**
+  @section license License
+
+  Internal traffic_ctl request/responses definitions.
+
+  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
+
+// We base on the common client types.
+#include "shared/rpc/RPCRequests.h"
+
+/// This file defines all the traffic_ctl API client request and responses objects needed to model the jsonrpc messages used in the
+/// TS JSONRPC Node API.
+
+///
+/// @brief Models the record request message to fetch all records by type.
+///
+struct GetAllRecordsRequest : shared::rpc::RecordLookupRequest {
+  using super = shared::rpc::RecordLookupRequest;
+  GetAllRecordsRequest(bool const configs) : super()
+  {
+    super::emplace_rec(".*", shared::rpc::REGEX, (configs ? shared::rpc::CONFIG_REC_TYPES : shared::rpc::METRIC_REC_TYPES));
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief Models the config reload request. No params are needed.
+///
+struct ConfigReloadRequest : shared::rpc::ClientRequest {
+  std::string
+  get_method() const override
+  {
+    return "admin_config_reload";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief To fetch config file registry from the RPC node.
+///
+struct ConfigShowFileRegistryRequest : shared::rpc::ClientRequest {
+  std::string
+  get_method() const override
+  {
+    return "filemanager.get_files_registry";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief Models the clear 'all' metrics request.
+///
+struct ClearAllMetricRequest : shared::rpc::ClientRequest {
+  std::string
+  get_method() const override
+  {
+    return "admin_clear_all_metrics_records";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief Models the clear metrics request.
+///
+struct ClearMetricRequest : shared::rpc::ClientRequest {
+  using super = shared::rpc::ClientRequest;
+  struct Params {
+    std::vector<std::string> names; //!< client expects a list of record names.
+  };
+  ClearMetricRequest(Params p) { super::params = p; }
+  std::string
+  get_method() const override
+  {
+    return "admin_clear_metrics_records";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct ConfigSetRecordRequest : shared::rpc::ClientRequest {
+  struct Params {
+    std::string recName;
+    std::string recValue;
+  };
+  using super = shared::rpc::ClientRequest;
+  ConfigSetRecordRequest(Params d) { super::params.push_back(d); }
+  std::string
+  get_method() const override
+  {
+    return "admin_config_set_records";
+  }
+};
+struct ConfigSetRecordResponse {
+  struct UpdatedRec {
+    std::string recName;
+    std::string updateType;
+  };
+  std::vector<UpdatedRec> data;
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct HostStatusLookUpResponse {
+  struct HostStatusInfo {
+    std::string hostName;
+    std::string status;
+  };
+
+  std::vector<HostStatusInfo> statusList;
+  std::vector<std::string> errorList;
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct HostSetStatusRequest : shared::rpc::ClientRequest {
+  using super = shared::rpc::ClientRequest;
+  struct Params {
+    enum class Op : short {
+      UP = 1,
+      DOWN,
+    };
+    Op op;
+    std::vector<std::string> hosts;
+    std::string reason;
+    std::string time{"0"};
+  };
+
+  HostSetStatusRequest(Params p) { super::params = p; }
+  std::string
+  get_method() const override
+  {
+    return "admin_host_set_status";
+  }
+};
+
+struct HostGetStatusRequest : shared::rpc::ClientRequest {
+  using super  = shared::rpc::ClientRequest;
+  using Params = std::vector<std::string>;
+  HostGetStatusRequest(Params p) { super::params = std::move(p); }
+
+  std::string
+  get_method() const override
+  {
+    return "admin_host_get_status";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct BasicPluginMessageRequest : shared::rpc::ClientRequest {
+  using super = BasicPluginMessageRequest;
+  struct Params {
+    std::string tag;
+    std::string str;
+  };
+  BasicPluginMessageRequest(Params p) { super::params = p; }
+  std::string
+  get_method() const override
+  {
+    return "admin_plugin_send_basic_msg";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct ServerStartDrainRequest : shared::rpc::ClientRequest {
+  using super = shared::rpc::ClientRequest;
+  struct Params {
+    bool waitForNewConnections{false};
+  };
+  ServerStartDrainRequest(Params p) { super::params = p; }
+  std::string
+  get_method() const override
+  {
+    return "admin_server_start_drain";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct ServerStopDrainRequest : shared::rpc::ClientRequest {
+  using super = ServerStopDrainRequest;
+  std::string
+  get_method() const override
+  {
+    return "admin_server_stop_drain";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct SetStorageDeviceOfflineRequest : shared::rpc::ClientRequest {
+  using super = shared::rpc::ClientRequest;
+  struct Params {
+    std::vector<std::string> names;
+  };
+  SetStorageDeviceOfflineRequest(Params p) { super::params = p; }
+  std::string
+  get_method() const override
+  {
+    return "admin_storage_set_device_offline";
+  }
+};
+
+//------------------------------------------------------------------------------------------------------------------------------------
+struct GetStorageDeviceStatusRequest : shared::rpc::ClientRequest {
+  using super = shared::rpc::ClientRequest;
+  struct Params {
+    std::vector<std::string> names;
+  };
+  GetStorageDeviceStatusRequest(Params p) { super::params = p; }
+  std::string
+  get_method() const override
+  {
+    return "admin_storage_get_device_status";
+  }
+};
+
+struct DeviceStatusInfoResponse {
+  struct CacheDisk {
+    CacheDisk(std::string p, std::string s, int e) : path(std::move(p)), status(std::move(s)), errorCount(e) {}
+    std::string path;
+    std::string status;
+    int errorCount;
+  };
+  std::vector<CacheDisk> data;
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+struct ShowRegisterHandlersRequest : shared::rpc::ClientRequest {
+  using super = shared::rpc::ClientRequest;
+  std::string
+  get_method() const override
+  {
+    return "show_registered_handlers";
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+///
+/// @brief Config status request mapping class.
+///
+/// There is no interaction between the traffic_ctl and this class so all the variables are defined in this
+/// class.
+///
+struct ConfigStatusRequest : shared::rpc::RecordLookupRequest {
+  using super = shared::rpc::RecordLookupRequest;
+  ConfigStatusRequest() : super()
+  {
+    static const std::array<std::string, 6> statusFieldsNames = {
+      "proxy.process.version.server.long",        "proxy.node.restarts.proxy.start_time",
+      "proxy.node.config.reconfigure_time",       "proxy.node.config.reconfigure_required",
+      "proxy.node.config.restart_required.proxy", "proxy.node.config.restart_required.manager"};
+    for (auto &&recordName : statusFieldsNames) {
+      super::emplace_rec(recordName, shared::rpc::NOT_REGEX, shared::rpc::METRIC_REC_TYPES);
+    }
+  }
+};
diff --git a/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h b/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h
new file mode 100644
index 0000000..e268497
--- /dev/null
+++ b/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h
@@ -0,0 +1,179 @@
+/**
+  @section license License
+
+  traffic_ctl yaml codecs.
+
+  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
+
+// base yaml codecs.
+#include "shared/rpc/yaml_codecs.h"
+
+#include "CtrlRPCRequests.h"
+
+// traffic_ctl jsonrpc request/response YAML codec implementation.
+
+namespace YAML
+{
+template <> struct convert<ConfigSetRecordRequest::Params> {
+  static Node
+  encode(ConfigSetRecordRequest::Params const &params)
+  {
+    Node node;
+    node["record_name"]  = params.recName;
+    node["record_value"] = params.recValue;
+
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<HostSetStatusRequest::Params::Op> {
+  static Node
+  encode(HostSetStatusRequest::Params::Op const &op)
+  {
+    using Op = HostSetStatusRequest::Params::Op;
+    switch (op) {
+    case Op::UP:
+      return Node("up");
+    case Op::DOWN:
+      return Node("down");
+    }
+    return Node("unknown");
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<HostSetStatusRequest::Params> {
+  static Node
+  encode(HostSetStatusRequest::Params const &params)
+  {
+    Node node;
+    node["operation"] = params.op;
+    node["host"]      = params.hosts; // list
+    node["reason"]    = params.reason;
+    node["time"]      = params.time;
+
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<ClearMetricRequest::Params> {
+  static Node
+  encode(ClearMetricRequest::Params const &params)
+  {
+    Node node;
+    for (auto name : params.names) {
+      Node n;
+      n["record_name"] = name;
+      node.push_back(n);
+    }
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<BasicPluginMessageRequest::Params> {
+  static Node
+  encode(BasicPluginMessageRequest::Params const &params)
+  {
+    Node node;
+    node["tag"]  = params.tag;
+    node["data"] = params.str;
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<ServerStartDrainRequest::Params> {
+  static Node
+  encode(ServerStartDrainRequest::Params const &params)
+  {
+    Node node;
+    node["no_new_connections"] = params.waitForNewConnections;
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<SetStorageDeviceOfflineRequest::Params> {
+  static Node
+  encode(SetStorageDeviceOfflineRequest::Params const &params)
+  {
+    Node node;
+    for (auto &&path : params.names) {
+      node.push_back(path);
+    }
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<GetStorageDeviceStatusRequest::Params> {
+  static Node
+  encode(GetStorageDeviceStatusRequest::Params const &params)
+  {
+    Node node;
+    for (auto &&path : params.names) {
+      node.push_back(path);
+    }
+    return node;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<DeviceStatusInfoResponse> {
+  static bool
+  decode(Node const &node, DeviceStatusInfoResponse &info)
+  {
+    for (auto &&item : node) {
+      Node disk = item["cachedisk"];
+      info.data.emplace_back(helper::try_extract<std::string>(disk, "path"),   // path
+                             helper::try_extract<std::string>(disk, "status"), // status
+                             helper::try_extract<int>(disk, "error_count")     // err count
+
+      );
+    }
+    return true;
+  }
+};
+//------------------------------------------------------------------------------------------------------------------------------------
+template <> struct convert<ConfigSetRecordResponse> {
+  static bool
+  decode(Node const &node, ConfigSetRecordResponse &info)
+  {
+    for (auto &&item : node) {
+      info.data.push_back(
+        {helper::try_extract<std::string>(item, "record_name"), helper::try_extract<std::string>(item, "update_type")});
+    }
+    return true;
+  }
+};
+
+template <> struct convert<HostStatusLookUpResponse> {
+  static bool
+  decode(Node const &node, HostStatusLookUpResponse &info)
+  {
+    YAML::Node statusList = node["statusList"];
+    YAML::Node errorList  = node["errorList"];
+    for (auto &&item : statusList) {
+      HostStatusLookUpResponse::HostStatusInfo hi;
+      hi.hostName = item["hostname"].Scalar();
+      hi.status   = item["status"].Scalar();
+      info.statusList.push_back(hi);
+    }
+    for (auto &&item : errorList) {
+      info.errorList.push_back(item.Scalar());
+    }
+    return true;
+  }
+};
+} // namespace YAML
diff --git a/src/traffic_ctl/metric.cc b/src/traffic_ctl/metric.cc
deleted file mode 100644
index 1076ace..0000000
--- a/src/traffic_ctl/metric.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-/** @file
-
-  traffic_ctl
-
-  @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 "traffic_ctl.h"
-#include "records/P_RecUtils.h"
-
-void
-CtrlEngine::metric_get()
-{
-  for (const auto &it : arguments.get("get")) {
-    CtrlMgmtRecord record;
-    TSMgmtError error;
-
-    error = record.fetch(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    if (REC_TYPE_IS_STAT(record.rclass())) {
-      std::cout << record.name() << ' ' << CtrlMgmtRecordValue(record).c_str() << std::endl;
-    }
-  }
-}
-
-void
-CtrlEngine::metric_match()
-{
-  for (const auto &it : arguments.get("match")) {
-    CtrlMgmtRecordList reclist;
-    TSMgmtError error;
-
-    error = reclist.match(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-
-    while (!reclist.empty()) {
-      CtrlMgmtRecord record(reclist.next());
-      if (REC_TYPE_IS_STAT(record.rclass())) {
-        std::cout << record.name() << ' ' << CtrlMgmtRecordValue(record).c_str() << std::endl;
-      }
-    }
-  }
-}
-
-void
-CtrlEngine::metric_clear()
-{
-  TSMgmtError error;
-
-  error = TSStatsReset(nullptr);
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to clear metrics");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
-
-void
-CtrlEngine::metric_zero()
-{
-  TSMgmtError error;
-
-  for (const auto &it : arguments.get("zero")) {
-    error = TSStatsReset(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to clear %s", it.c_str());
-      status_code = CTRL_EX_ERROR;
-    }
-  }
-}
diff --git a/src/traffic_ctl/plugin.cc b/src/traffic_ctl/plugin.cc
deleted file mode 100644
index a9eb08b..0000000
--- a/src/traffic_ctl/plugin.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-/** @file
-
- Plugin related sub commands.
-
-  @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 "traffic_ctl.h"
-
-void
-CtrlEngine::plugin_msg()
-{
-  TSMgmtError error;
-  auto msg_data = arguments.get("msg");
-
-  if (msg_data.size() == 1) { // no data provided, just the tag
-    error = TSLifecycleMessage(msg_data[0].c_str(), nullptr, 0);
-  } else {
-    error = TSLifecycleMessage(msg_data[0].c_str(), msg_data[1].c_str(), msg_data[1].size() + 1);
-  }
-
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "message '%s' not sent", msg_data[0].c_str());
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
diff --git a/src/traffic_ctl/server.cc b/src/traffic_ctl/server.cc
deleted file mode 100644
index 58271ba..0000000
--- a/src/traffic_ctl/server.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-/** @file
-
-  traffic_ctl
-
-  @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 "traffic_ctl.h"
-
-void
-CtrlEngine::server_restart()
-{
-  TSMgmtError error;
-  unsigned flags = TS_RESTART_OPT_NONE;
-
-  if (arguments.get("drain")) {
-    flags |= TS_RESTART_OPT_DRAIN;
-  }
-
-  if (arguments.get("manager")) {
-    error = TSRestart(flags);
-  } else {
-    error = TSBounce(flags);
-  }
-
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "server restart failed");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
-
-void
-CtrlEngine::server_backtrace()
-{
-  TSMgmtError error;
-  TSString trace = nullptr;
-
-  error = TSProxyBacktraceGet(0, &trace);
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "server backtrace failed");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-
-  std::cout << trace << std::endl;
-  TSfree(trace);
-}
-
-void
-CtrlEngine::server_status()
-{
-  switch (TSProxyStateGet()) {
-  case TS_PROXY_ON:
-    std::cout << "Proxy -- on" << std::endl;
-    break;
-  case TS_PROXY_OFF:
-    std::cout << "Proxy -- off" << std::endl;
-    break;
-  case TS_PROXY_UNDEFINED:
-    std::cout << "Proxy status undefined" << std::endl;
-    break;
-  }
-}
-
-void
-CtrlEngine::server_stop()
-{
-  TSMgmtError error;
-  unsigned flags = TS_RESTART_OPT_NONE;
-
-  if (arguments.get("drain")) {
-    flags |= TS_STOP_OPT_DRAIN;
-  }
-
-  error = TSStop(flags);
-
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "server stop failed");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
-
-void
-CtrlEngine::server_start()
-{
-  TSMgmtError error;
-  unsigned clear = TS_CACHE_CLEAR_NONE;
-
-  clear |= arguments.get("clear-cache") ? TS_CACHE_CLEAR_CACHE : TS_CACHE_CLEAR_NONE;
-  clear |= arguments.get("clear-hostdb") ? TS_CACHE_CLEAR_HOSTDB : TS_CACHE_CLEAR_NONE;
-
-  error = TSProxyStateSet(TS_PROXY_ON, clear);
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "server start failed");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
-
-void
-CtrlEngine::server_drain()
-{
-  TSMgmtError error;
-
-  if (arguments.get("undo")) {
-    error = TSDrain(TS_DRAIN_OPT_UNDO);
-  } else if (arguments.get("no-new-connection")) {
-    error = TSDrain(TS_DRAIN_OPT_IDLE);
-  } else {
-    error = TSDrain(TS_DRAIN_OPT_NONE);
-  }
-
-  if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "server drain failed");
-    status_code = CTRL_EX_ERROR;
-    return;
-  }
-}
diff --git a/src/traffic_ctl/storage.cc b/src/traffic_ctl/storage.cc
deleted file mode 100644
index 1e55bc9..0000000
--- a/src/traffic_ctl/storage.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-/** @file
-
-  traffic_ctl
-
-  @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 "traffic_ctl.h"
-
-void
-CtrlEngine::storage_offline()
-{
-  auto offline_data = arguments.get("offline");
-  for (const auto &it : offline_data) {
-    TSMgmtError error;
-
-    error = TSStorageDeviceCmdOffline(it.c_str());
-    if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to take %s offline", offline_data[0].c_str());
-      status_code = CTRL_EX_ERROR;
-      return;
-    }
-  }
-}
diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc
index 6c6ef2b..06a1b44 100644
--- a/src/traffic_ctl/traffic_ctl.cc
+++ b/src/traffic_ctl/traffic_ctl.cc
@@ -21,281 +21,178 @@
   limitations under the License.
  */
 
-#include "traffic_ctl.h"
+#include <iostream>
 
-#include "records/I_RecProcess.h"
-#include "RecordsConfig.h"
 #include "tscore/I_Layout.h"
 #include "tscore/runroot.h"
+#include "tscore/ArgParser.h"
 
-const char *
-CtrlMgmtRecord::name() const
-{
-  return this->ele->rec_name;
-}
+#include "CtrlCommands.h"
 
-TSRecordT
-CtrlMgmtRecord::type() const
-{
-  return this->ele->rec_type;
-}
+constexpr int CTRL_EX_OK            = 0;
+constexpr int CTRL_EX_ERROR         = 2;
+constexpr int CTRL_EX_UNIMPLEMENTED = 3;
 
-int
-CtrlMgmtRecord::rclass() const
-{
-  return this->ele->rec_class;
-}
-
-int64_t
-CtrlMgmtRecord::as_int() const
-{
-  switch (this->ele->rec_type) {
-  case TS_REC_INT:
-    return this->ele->valueT.int_val;
-  case TS_REC_COUNTER:
-    return this->ele->valueT.counter_val;
-  default:
-    return 0;
-  }
-}
-
-TSMgmtError
-CtrlMgmtRecord::fetch(const char *name)
-{
-  return TSRecordGet(name, this->ele);
-}
-
-TSMgmtError
-CtrlMgmtRecordList::match(const char *name)
-{
-  return TSRecordGetMatchMlt(name, this->list);
-}
-
-CtrlMgmtRecordValue::CtrlMgmtRecordValue(const CtrlMgmtRecord &rec)
-{
-  this->init(rec.ele->rec_type, rec.ele->valueT);
-}
-
-CtrlMgmtRecordValue::CtrlMgmtRecordValue(const TSRecordEle *ele)
-{
-  this->init(ele->rec_type, ele->valueT);
-}
-
-CtrlMgmtRecordValue::CtrlMgmtRecordValue(TSRecordT _t, TSRecordValueT _v)
-{
-  this->init(_t, _v);
-}
-
-void
-CtrlMgmtRecordValue::init(TSRecordT _t, TSRecordValueT _v)
-{
-  this->rec_type = _t;
-  switch (this->rec_type) {
-  case TS_REC_INT:
-    snprintf(this->fmt.nbuf, sizeof(this->fmt.nbuf), "%" PRId64, _v.int_val);
-    break;
-  case TS_REC_COUNTER:
-    snprintf(this->fmt.nbuf, sizeof(this->fmt.nbuf), "%" PRId64, _v.counter_val);
-    break;
-  case TS_REC_FLOAT:
-    snprintf(this->fmt.nbuf, sizeof(this->fmt.nbuf), "%f", _v.float_val);
-    break;
-  case TS_REC_STRING:
-    if (strcmp(_v.string_val, "") == 0) {
-      this->fmt.str = "\"\"";
-    } else {
-      this->fmt.str = _v.string_val;
-    }
-    break;
-  default:
-    rec_type      = TS_REC_STRING;
-    this->fmt.str = "(invalid)";
-  }
-}
-
-const char *
-CtrlMgmtRecordValue::c_str() const
-{
-  switch (this->rec_type) {
-  case TS_REC_STRING:
-    return this->fmt.str;
-  default:
-    return this->fmt.nbuf;
-  }
-}
-
-void
-CtrlMgmtError(TSMgmtError err, const char *fmt, ...)
-{
-  ats_scoped_str msg(TSGetErrorMessage(err));
-
-  if (fmt) {
-    va_list ap;
-
-    fprintf(stderr, "%s: ", program_name);
-    va_start(ap, fmt);
-    vfprintf(stderr, fmt, ap);
-    va_end(ap);
-
-    fprintf(stderr, ": %s\n", (const char *)msg);
-  } else {
-    fprintf(stderr, "%s: %s\n", program_name, (const char *)msg);
-  }
-}
-
-void
-CtrlEngine::CtrlUnimplementedCommand(std::string_view command)
-{
-  fprintf(stderr, "'%s' command is not implemented\n", command.data());
-  status_code = CTRL_EX_UNIMPLEMENTED;
-}
+int status_code{CTRL_EX_OK};
 
 int
 main(int argc, const char **argv)
 {
-  CtrlEngine engine;
+  ts::ArgParser parser;
 
-  engine.parser.add_global_usage("traffic_ctl [OPTIONS] CMD [ARGS ...]");
-  engine.parser.require_commands();
+  std::shared_ptr<CtrlCommand> command;
 
-  engine.parser.add_option("--debug", "", "Enable debugging output")
+  auto CtrlUnimplementedCommand = [](std::string_view cmd) {
+    std::cout << "Command " << cmd << " unimplemented.\n";
+    status_code = CTRL_EX_UNIMPLEMENTED;
+  };
+
+  parser.add_description("Apache Traffic Server RPC CLI");
+  parser.add_global_usage("traffic_ctl [OPTIONS] CMD [ARGS ...]");
+  parser.require_commands();
+
+  parser.add_option("--debug", "", "Enable debugging output - unimplemented")
     .add_option("--version", "-V", "Print version string")
     .add_option("--help", "-h", "Print usage information")
-    .add_option("--run-root", "", "using TS_RUNROOT as sandbox", "TS_RUNROOT", 1);
+    .add_option("--run-root", "", "using TS_RUNROOT as sandbox", "TS_RUNROOT", 1)
+    .add_option("--format", "-f", "Use a specific output format {legacy|pretty|json|rpc}", "", 1, "legacy", "format");
 
-  auto &alarm_command   = engine.parser.add_command("alarm", "Manipulate alarms").require_commands();
-  auto &config_command  = engine.parser.add_command("config", "Manipulate configuration records").require_commands();
-  auto &metric_command  = engine.parser.add_command("metric", "Manipulate performance metrics").require_commands();
-  auto &server_command  = engine.parser.add_command("server", "Stop, restart and examine the server").require_commands();
-  auto &storage_command = engine.parser.add_command("storage", "Manipulate cache storage").require_commands();
-  auto &plugin_command  = engine.parser.add_command("plugin", "Interact with plugins").require_commands();
-  auto &host_command    = engine.parser.add_command("host", "Interact with host status").require_commands();
-
-  // alarm commands
-  alarm_command.add_command("clear", "Clear all current alarms", [&]() { engine.alarm_clear(); })
-    .add_example_usage("traffic_ctl alarm clear");
-  alarm_command.add_command("list", "List all current alarms", [&]() { engine.alarm_list(); })
-    .add_example_usage("traffic_ctl alarm list");
-  alarm_command.add_command("resolve", "Resolve the listed alarms", "", MORE_THAN_ONE_ARG_N, [&]() { engine.alarm_resolve(); })
-    .add_example_usage("traffic_ctl alarm resolve ALARM [ALARM ...]");
+  auto &config_command     = parser.add_command("config", "Manipulate configuration records").require_commands();
+  auto &metric_command     = parser.add_command("metric", "Manipulate performance metrics").require_commands();
+  auto &server_command     = parser.add_command("server", "Stop, restart and examine the server").require_commands();
+  auto &storage_command    = parser.add_command("storage", "Manipulate cache storage").require_commands();
+  auto &plugin_command     = parser.add_command("plugin", "Interact with plugins").require_commands();
+  auto &host_command       = parser.add_command("host", "Interact with host status").require_commands();
+  auto &direct_rpc_command = parser.add_command("rpc", "Interact with the rpc api").require_commands();
 
   // config commands
-  config_command.add_command("defaults", "Show default information configuration values", [&]() { engine.config_defaults(); })
+  config_command.add_command("defaults", "Show default information configuration values", [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config defaults [OPTIONS]")
     .add_option("--records", "", "Emit output in records.config format");
   config_command
     .add_command("describe", "Show detailed information about configuration values", "", MORE_THAN_ONE_ARG_N,
-                 [&]() { engine.config_describe(); })
+                 [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config describe RECORD [RECORD ...]");
-  config_command.add_command("diff", "Show non-default configuration values", [&]() { engine.config_diff(); })
+  config_command.add_command("diff", "Show non-default configuration values", [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config diff [OPTIONS]")
     .add_option("--records", "", "Emit output in records.config format");
-  config_command.add_command("get", "Get one or more configuration values", "", MORE_THAN_ONE_ARG_N, [&]() { engine.config_get(); })
+  config_command.add_command("get", "Get one or more configuration values", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config get [OPTIONS] RECORD [RECORD ...]")
     .add_option("--records", "", "Emit output in records.config format");
   config_command
-    .add_command("match", "Get configuration matching a regular expression", "", MORE_THAN_ONE_ARG_N,
-                 [&]() { engine.config_match(); })
+    .add_command("match", "Get configuration matching a regular expression", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config match [OPTIONS] REGEX [REGEX ...]")
     .add_option("--records", "", "Emit output in records.config format");
-  config_command.add_command("reload", "Request a configuration reload", [&]() { engine.config_reload(); })
+  config_command.add_command("reload", "Request a configuration reload", [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config reload");
-  config_command.add_command("status", "Check the configuration status", [&]() { engine.config_status(); })
+  config_command.add_command("status", "Check the configuration status", [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config status");
-  config_command.add_command("set", "Set a configuration value", "", 2, [&]() { engine.config_set(); })
+  config_command.add_command("set", "Set a configuration value", "", 2, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config set RECORD VALUE");
 
+  config_command.add_command("registry", "Show configuration file registry", [&]() { command->execute(); })
+    .add_example_usage("traffic_ctl config registry");
   // host commands
-  host_command.add_command("status", "Get one or more host statuses", "", MORE_THAN_ONE_ARG_N, [&]() { engine.status_get(); })
+  host_command.add_command("status", "Get one or more host statuses", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl host status HOST  [HOST  ...]");
-  host_command.add_command("down", "Set down one or more host(s)", "", MORE_THAN_ONE_ARG_N, [&]() { engine.status_down(); })
+  host_command.add_command("down", "Set down one or more host(s)", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl host down HOST [OPTIONS]")
-    .add_option("--time", "-I", "number of seconds that a host is marked down", "", 1)
-    .add_option("--reason", "", "reason for marking the host down, one of 'manual|active|local", "", 1);
-  host_command.add_command("up", "Set up one or more host(s)", "", MORE_THAN_ONE_ARG_N, [&]() { engine.status_up(); })
+    .add_option("--time", "-I", "number of seconds that a host is marked down", "", 1, "0")
+    .add_option("--reason", "", "reason for marking the host down, one of 'manual|active|local", "", 1, "manual");
+  host_command.add_command("up", "Set up one or more host(s)", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl host up METRIC value")
-    .add_option("--reason", "", "reason for marking the host up, one of 'manual|active|local", "", 1);
+    .add_option("--reason", "", "reason for marking the host up, one of 'manual|active|local", "", 1, "manual");
 
   // metric commands
-  metric_command.add_command("get", "Get one or more metric values", "", MORE_THAN_ONE_ARG_N, [&]() { engine.metric_get(); })
+  metric_command.add_command("get", "Get one or more metric values", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl metric get METRIC [METRIC ...]");
-  metric_command.add_command("clear", "Clear all metric values", [&]() { engine.metric_clear(); });
+  metric_command.add_command("clear", "Clear all metric values", [&]() { command->execute(); });
   metric_command.add_command("describe", "Show detailed information about one or more metric values", "", MORE_THAN_ONE_ARG_N,
-                             [&]() { engine.CtrlUnimplementedCommand("describe"); }); // not implemented
+                             [&]() { command->execute(); }); // not implemented
   metric_command.add_command("match", "Get metrics matching a regular expression", "", MORE_THAN_ZERO_ARG_N,
-                             [&]() { engine.metric_match(); });
+                             [&]() { command->execute(); });
   metric_command.add_command("monitor", "Display the value of a metric over time", "", MORE_THAN_ZERO_ARG_N,
-                             [&]() { engine.CtrlUnimplementedCommand("monitor"); }); // not implemented
-  metric_command.add_command("zero", "Clear one or more metric values", "", MORE_THAN_ONE_ARG_N, [&]() { engine.metric_zero(); });
+                             [&]() { CtrlUnimplementedCommand("monitor"); }); // not implemented
+  metric_command.add_command("zero", "Clear one or more metric values", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); });
 
   // plugin command
   plugin_command
     .add_command("msg", "Send message to plugins - a TAG and the message DATA(optional)", "", MORE_THAN_ONE_ARG_N,
-                 [&]() { engine.plugin_msg(); })
+                 [&]() { command->execute(); })
     .add_example_usage("traffic_ctl plugin msg TAG DATA");
 
   // server commands
   server_command.add_command("backtrace", "Show a full stack trace of the traffic_server process",
-                             [&]() { engine.server_backtrace(); });
-  server_command.add_command("restart", "Restart Traffic Server", [&]() { engine.server_restart(); })
+                             [&]() { CtrlUnimplementedCommand("backtrace"); });
+  server_command.add_command("restart", "Restart Traffic Server", [&]() { CtrlUnimplementedCommand("restart"); })
     .add_example_usage("traffic_ctl server restart [OPTIONS]")
-    .add_option("--drain", "", "Wait for client connections to drain before restarting")
-    .add_option("--manager", "", "Restart traffic_manager as well as traffic_server");
-  server_command.add_command("start", "Start the proxy", [&]() { engine.server_start(); })
+    .add_option("--drain", "", "Wait for client connections to drain before restarting");
+  server_command.add_command("start", "Start the proxy", [&]() { CtrlUnimplementedCommand("start"); })
     .add_example_usage("traffic_ctl server start [OPTIONS]")
     .add_option("--clear-cache", "", "Clear the disk cache on startup")
     .add_option("--clear-hostdb", "", "Clear the DNS cache on startup");
-  server_command.add_command("status", "Show the proxy status", [&]() { engine.server_status(); })
+  server_command.add_command("status", "Show the proxy status", [&]() { CtrlUnimplementedCommand("status"); })
     .add_example_usage("traffic_ctl server status");
-  server_command.add_command("stop", "Stop the proxy", [&]() { engine.server_stop(); })
+  server_command.add_command("stop", "Stop the proxy", [&]() { CtrlUnimplementedCommand("stop"); })
     .add_example_usage("traffic_ctl server stop [OPTIONS]")
     .add_option("--drain", "", "Wait for client connections to drain before stopping");
-  server_command.add_command("drain", "Drain the requests", [&]() { engine.server_drain(); })
+  server_command.add_command("drain", "Drain the requests", [&]() { command->execute(); })
     .add_example_usage("traffic_ctl server drain [OPTIONS]")
     .add_option("--no-new-connection", "-N", "Wait for new connections down to threshold before starting draining")
     .add_option("--undo", "-U", "Recover server from the drain mode");
 
   // storage commands
   storage_command
-    .add_command("offline", "Take one or more storage volumes offline", "", MORE_THAN_ONE_ARG_N,
-                 [&]() { engine.storage_offline(); })
+    .add_command("offline", "Take one or more storage volumes offline", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("storage offline DEVICE [DEVICE ...]");
-  storage_command.add_command("status", "Show the storage configuration", "", MORE_THAN_ZERO_ARG_N,
-                              [&]() { engine.CtrlUnimplementedCommand("status"); }); // not implemented
+  storage_command.add_command("status", "Show the storage configuration", "", MORE_THAN_ONE_ARG_N,
+                              [&]() { command->execute(); }); // not implemented
 
-  // parse the arguments
-  engine.arguments = engine.parser.parse(argv);
+  // direct rpc commands, handy for debug and trouble shooting
+  direct_rpc_command
+    .add_command("file", "Send direct JSONRPC request to the server from a passed file(s)", "", MORE_THAN_ONE_ARG_N,
+                 [&]() { command->execute(); })
+    .add_example_usage("traffic_ctl rpc file request.yaml");
+  direct_rpc_command.add_command("get-api", "Request full API from server", "", 0, [&]() { command->execute(); })
+    .add_example_usage("traffic_ctl rpc get-api");
+  direct_rpc_command
+    .add_command("input", "Read from standard input. Ctrl-D to send the request", "", 0, [&]() { command->execute(); })
+    .add_option("--raw", "-r",
+                "No json/yaml parse validation will take place, the raw content will be directly send to the server.", "", 0, "",
+                "raw")
+    .add_example_usage("traffic_ctl rpc input ");
+  direct_rpc_command
+    .add_command("invoke", "Call a method by using the method name as input parameter", "", MORE_THAN_ONE_ARG_N,
+                 [&]() { command->execute(); })
+    .add_option("--params", "-p", "Parameters to be passed in the request, YAML or JSON format", "", MORE_THAN_ONE_ARG_N, "", "")
+    .add_example_usage("traffic_ctl rpc invoke foo_bar -p \"numbers: [1, 2, 3]\"");
 
-  BaseLogFile *base_log_file = new BaseLogFile("stderr");
-  DiagsPtr::set(new Diags("traffic_ctl", "" /* tags */, "" /* actions */, base_log_file));
+  try {
+    auto args = parser.parse(argv);
+    argparser_runroot_handler(args.get("run-root").value(), argv[0]);
+    Layout::create();
 
-  if (engine.arguments.get("debug")) {
-    diags()->activate_taglist("traffic_ctl", DiagsTagType_Debug);
-    diags()->config.enabled(DiagsTagType_Debug, 1);
-    diags()->show_location = SHOW_LOCATION_DEBUG;
+    if (args.get("config")) {
+      command = std::make_shared<ConfigCommand>(&args);
+    } else if (args.get("metric")) {
+      command = std::make_shared<MetricCommand>(&args);
+    } else if (args.get("server")) {
+      command = std::make_shared<ServerCommand>(&args);
+    } else if (args.get("storage")) {
+      command = std::make_shared<StorageCommand>(&args);
+    } else if (args.get("plugin")) {
+      command = std::make_shared<PluginCommand>(&args);
+    } else if (args.get("host")) {
+      command = std::make_shared<HostCommand>(&args);
+    } else if (args.get("rpc")) {
+      command = std::make_shared<DirectRPCCommand>(&args);
+    }
+    // Execute
+    args.invoke();
+  } catch (std::exception const &ex) {
+    status_code = CTRL_EX_ERROR;
+    std::cerr << "Error found:\n" << ex.what() << '\n';
   }
 
-  argparser_runroot_handler(engine.arguments.get("run-root").value(), argv[0]);
-  Layout::create();
-
-  // This is a little bit of a hack, for now it'll suffice.
-  max_records_entries = 262144;
-  RecProcessInit(RECM_STAND_ALONE, diags());
-  LibRecordsConfigInit();
-
-  ats_scoped_str rundir(RecConfigReadRuntimeDir());
-
-  // Make a best effort to connect the control socket. If it turns out we are
-  // just displaying help or something then it
-  // doesn't matter that we failed. If we end up performing some operation then
-  // that operation will fail and display the
-  // error.
-  TSInit(rundir, static_cast<TSInitOptionT>(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
-
-  engine.arguments.invoke();
-
-  // Done with the mgmt API.
-  TSTerminate();
-
-  return engine.status_code;
+  return status_code;
 }
diff --git a/src/traffic_ctl/traffic_ctl.h b/src/traffic_ctl/traffic_ctl.h
deleted file mode 100644
index 94073f6..0000000
--- a/src/traffic_ctl/traffic_ctl.h
+++ /dev/null
@@ -1,220 +0,0 @@
-/** @file
-
-  traffic_ctl
-
-  @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.
- */
-
-#pragma once
-
-#include "tscore/ink_platform.h"
-#include "tscore/Diags.h"
-#include "tscore/ink_memory.h"
-#include "mgmtapi.h"
-#include "tscore/ink_args.h"
-#include "tscore/I_Version.h"
-#include "tscore/BaseLogFile.h"
-#include "tscore/ArgParser.h"
-
-#include <vector>
-#include <string>
-#include <iostream>
-
-// Exit status codes, following BSD's sysexits(3)
-constexpr int CTRL_EX_OK            = 0;
-constexpr int CTRL_EX_ERROR         = 2;
-constexpr int CTRL_EX_UNIMPLEMENTED = 3;
-constexpr int CTRL_EX_USAGE         = EX_USAGE;
-constexpr int CTRL_EX_UNAVAILABLE   = 69;
-
-#define CtrlDebug(...) Debug("traffic_ctl", __VA_ARGS__)
-
-void CtrlMgmtError(TSMgmtError err, const char *fmt, ...) TS_PRINTFLIKE(2, 3);
-
-#define CTRL_MGMT_CHECK(expr)                          \
-  do {                                                 \
-    TSMgmtError e = (expr);                            \
-    if (e != TS_ERR_OKAY) {                            \
-      CtrlDebug("%s failed with status %d", #expr, e); \
-      CtrlMgmtError(e, nullptr);                       \
-      status_code = CTRL_EX_ERROR;                     \
-      return;                                          \
-    }                                                  \
-  } while (0)
-
-struct CtrlMgmtRecord {
-  explicit CtrlMgmtRecord(TSRecordEle *e) : ele(e) {}
-  CtrlMgmtRecord() : ele(TSRecordEleCreate()) {}
-  ~CtrlMgmtRecord()
-  {
-    if (this->ele) {
-      TSRecordEleDestroy(this->ele);
-    }
-  }
-
-  TSMgmtError fetch(const char *);
-
-  const char *name() const;
-  TSRecordT type() const;
-  int rclass() const;
-  int64_t as_int() const;
-
-  // noncopyable
-  CtrlMgmtRecord(const CtrlMgmtRecord &) = delete;            // disabled
-  CtrlMgmtRecord &operator=(const CtrlMgmtRecord &) = delete; // disabled
-
-private:
-  TSRecordEle *ele;
-
-  friend struct CtrlMgmtRecordValue;
-};
-
-struct CtrlMgmtRecordValue {
-  explicit CtrlMgmtRecordValue(const TSRecordEle *);
-  explicit CtrlMgmtRecordValue(const CtrlMgmtRecord &);
-
-  CtrlMgmtRecordValue(TSRecordT, TSRecordValueT);
-  const char *c_str() const;
-
-  // noncopyable
-  CtrlMgmtRecordValue(const CtrlMgmtRecordValue &) = delete;            // disabled
-  CtrlMgmtRecordValue &operator=(const CtrlMgmtRecordValue &) = delete; // disabled
-
-private:
-  void init(TSRecordT, TSRecordValueT);
-
-  TSRecordT rec_type;
-  union {
-    const char *str;
-    char nbuf[32];
-  } fmt;
-};
-
-struct RecordListPolicy {
-  typedef TSRecordEle *entry_type;
-
-  static void
-  free(entry_type e)
-  {
-    TSRecordEleDestroy(e);
-  }
-
-  static entry_type
-  cast(void *ptr)
-  {
-    return (entry_type)ptr;
-  }
-};
-
-template <typename T> struct CtrlMgmtList {
-  CtrlMgmtList() : list(TSListCreate()) {}
-  ~CtrlMgmtList()
-  {
-    this->clear();
-    TSListDestroy(this->list);
-  }
-
-  bool
-  empty() const
-  {
-    return TSListIsEmpty(this->list);
-  }
-
-  void
-  clear() const
-  {
-    while (!this->empty()) {
-      T::free(T::cast(TSListDequeue(this->list)));
-    }
-  }
-
-  // Return (ownership of) the next list entry.
-  typename T::entry_type
-  next()
-  {
-    return T::cast(TSListDequeue(this->list));
-  }
-
-  TSList list;
-
-  // noncopyable
-  CtrlMgmtList(const CtrlMgmtList &) = delete;            // disabled
-  CtrlMgmtList &operator=(const CtrlMgmtList &) = delete; // disabled
-};
-
-struct CtrlMgmtRecordList : CtrlMgmtList<RecordListPolicy> {
-  TSMgmtError match(const char *);
-};
-
-// this is a engine for traffic_ctl containing the ArgParser and all the methods
-// it also has a status code which can be set by these methods to return
-struct CtrlEngine {
-  // the parser for traffic_ctl
-  ts::ArgParser parser;
-  // parsed arguments
-  ts::Arguments arguments;
-  // the return status code from functions
-  // By default it is set to CTRL_EX_OK so we don't need to set it
-  // in each method when they finish successfully.
-  int status_code = CTRL_EX_OK;
-
-  // All traffic_ctl methods:
-  // unimplemented command
-  void CtrlUnimplementedCommand(std::string_view command);
-
-  // alarm methods
-  void alarm_list();
-  void alarm_clear();
-  void alarm_resolve();
-
-  // config methods
-  void config_defaults();
-  void config_diff();
-  void config_status();
-  void config_reload();
-  void config_match();
-  void config_get();
-  void config_set();
-  void config_describe();
-
-  // host methods
-  void status_get();
-  void status_down();
-  void status_up();
-
-  // metric methods
-  void metric_get();
-  void metric_match();
-  void metric_clear();
-  void metric_zero();
-
-  // metric methods
-  void plugin_msg();
-
-  // server methods
-  void server_restart();
-  void server_backtrace();
-  void server_status();
-  void server_stop();
-  void server_start();
-  void server_drain();
-
-  // storage methods
-  void storage_offline();
-};
diff --git a/src/traffic_layout/Makefile.inc b/src/traffic_layout/Makefile.inc
index bc8952d..5f636c5 100644
--- a/src/traffic_layout/Makefile.inc
+++ b/src/traffic_layout/Makefile.inc
@@ -24,12 +24,13 @@
     -I$(abs_top_srcdir)/lib \
     -I$(abs_top_srcdir)/mgmt \
     -I$(abs_top_srcdir)/mgmt/utils \
+    @SWOC_INCLUDES@ \
     @YAMLCPP_INCLUDES@ \
     $(TS_INCLUDES)
 
 traffic_layout_traffic_layout_LDFLAGS =	\
     $(AM_LDFLAGS) \
-    @YAMLCPP_LDFLAGS@ $(BROTLIENC_LIB)
+    @SWOC_LDFLAGS@ @YAMLCPP_LDFLAGS@ $(BROTLIENC_LIB)
 
 traffic_layout_traffic_layout_SOURCES = \
 	traffic_layout/traffic_layout.cc \
@@ -47,4 +48,4 @@
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	@HWLOC_LIBS@ @YAMLCPP_LIBS@ @LIBLZMA@
+	@SWOC_LIBS@ @HWLOC_LIBS@ @YAMLCPP_LIBS@ @LIBLZMA@
diff --git a/src/traffic_layout/file_system.cc b/src/traffic_layout/file_system.cc
index b56c13c..ca727d6 100644
--- a/src/traffic_layout/file_system.cc
+++ b/src/traffic_layout/file_system.cc
@@ -41,9 +41,17 @@
 CopyStyle copy_style;
 
 // list of all executables of traffic server
-std::set<std::string> const executables = {"traffic_crashlog", "traffic_ctl",     "traffic_layout", "traffic_logcat",
-                                           "traffic_logstats", "traffic_manager", "traffic_server", "traffic_top",
-                                           "traffic_via",      "trafficserver",   "tspush",         "tsxs"};
+std::set<std::string> const executables = {"traffic_crashlog",
+                                           "traffic_ctl",
+                                           "traffic_layout",
+                                           "traffic_logcat",
+                                           "traffic_logstats",
+                                           "traffic_server",
+                                           "traffic_top",
+                                           "traffic_via",
+                                           "trafficserver",
+                                           "tspush",
+                                           "tsxs"};
 
 void
 append_slash(std::string &path)
diff --git a/src/traffic_logcat/Makefile.inc b/src/traffic_logcat/Makefile.inc
index 748961c..581e280 100644
--- a/src/traffic_logcat/Makefile.inc
+++ b/src/traffic_logcat/Makefile.inc
@@ -29,7 +29,7 @@
 	-I$(abs_top_srcdir)/proxy/shared \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
-	$(TS_INCLUDES)
+	@SWOC_INCLUDES@ $(TS_INCLUDES)
 
 traffic_logcat_traffic_logcat_LDFLAGS = \
 	$(AM_LDFLAGS) \
@@ -50,6 +50,6 @@
 	$(top_builddir)/src/tscpp/util/libtscpputil.la
 
 traffic_logcat_traffic_logcat_LDADD += \
-	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ @HWLOC_LIBS@ \
 	@YAMLCPP_LIBS@ \
 	@LIBPROFILER@ -lm
diff --git a/src/traffic_logstats/Makefile.inc b/src/traffic_logstats/Makefile.inc
index 242f7ec..1d0c1e9 100644
--- a/src/traffic_logstats/Makefile.inc
+++ b/src/traffic_logstats/Makefile.inc
@@ -29,11 +29,12 @@
 	-I$(abs_top_srcdir)/proxy/shared \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
+        @SWOC_INCLUDES@ \
 	$(TS_INCLUDES)
 
 traffic_logstats_traffic_logstats_LDFLAGS = \
 	$(AM_LDFLAGS) \
-	@YAMLCPP_LDFLAGS@
+	@SWOC_LDFLAGS@ @YAMLCPP_LDFLAGS@
 
 TESTS += \
 	traffic_logstats/tests/test_logstats_json \
@@ -54,6 +55,7 @@
 	$(top_builddir)/src/tscpp/util/libtscpputil.la
 
 traffic_logstats_traffic_logstats_LDADD += \
+  @SWOC_LIBS@ \
   @HWLOC_LIBS@ \
   @YAMLCPP_LIBS@ \
   @LIBPROFILER@ -lm
diff --git a/src/traffic_logstats/logstats.cc b/src/traffic_logstats/logstats.cc
index 75ab708..c9c151c 100644
--- a/src/traffic_logstats/logstats.cc
+++ b/src/traffic_logstats/logstats.cc
@@ -1280,7 +1280,7 @@
   if (!cl.no_format_check) {
     // Validate the fieldlist
     field                                = fieldlist->first();
-    const std::string_view test_fields[] = {"cqtq", "ttms", "chi", "crc", "pssc", "psql", "cqhm", "cquc", "caun", "phr", "shn"};
+    const std::string_view test_fields[] = {"cqtq", "ttms", "chi", "crc", "pssc", "psql", "cqhm", "pquc", "caun", "phr", "shn"};
     for (auto i : test_fields) {
       if (i != field->symbol()) {
         cerr << "Error parsing log file - expected field: " << i << ", but read field: " << field->symbol() << endl;
diff --git a/src/traffic_logstats/tests/logstats.blog b/src/traffic_logstats/tests/logstats.blog
index 72c09f7..b36e766 100644
--- a/src/traffic_logstats/tests/logstats.blog
+++ b/src/traffic_logstats/tests/logstats.blog
Binary files differ
diff --git a/src/traffic_manager/Makefile.inc b/src/traffic_manager/Makefile.inc
deleted file mode 100644
index 4e52a75..0000000
--- a/src/traffic_manager/Makefile.inc
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-#  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.
-
-bin_PROGRAMS += traffic_manager/traffic_manager
-
-traffic_manager_traffic_manager_CPPFLAGS = \
-    $(AM_CPPFLAGS) \
-	$(iocore_include_dirs) \
-	-I$(abs_top_srcdir)/include \
-	-I$(abs_top_srcdir)/lib \
-	-I$(abs_top_srcdir)/proxy/hdrs \
-	-I$(abs_top_srcdir)/proxy/shared \
-	-I$(abs_top_srcdir)/mgmt \
-	-I$(abs_top_srcdir)/mgmt/api \
-	-I$(abs_top_srcdir)/mgmt/api/include \
-	-I$(abs_top_srcdir)/mgmt/utils \
-	$(TS_INCLUDES)
-
-traffic_manager_traffic_manager_LDFLAGS = \
-	$(AM_LDFLAGS) \
-	@OPENSSL_LDFLAGS@ \
-	@YAMLCPP_LDFLAGS@
-
-traffic_manager_traffic_manager_SOURCES = \
-	traffic_manager/AddConfigFilesHere.cc \
-	traffic_manager/traffic_manager.cc
-
-traffic_manager_traffic_manager_LDADD = \
-	$(top_builddir)/mgmt/api/libmgmtapilocal.la \
-	$(top_builddir)/mgmt/libmgmt_lm.la \
-	$(top_builddir)/proxy/hdrs/libhdrs.a \
-	$(top_builddir)/src/tscore/libtscore.la \
-	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	$(top_builddir)/iocore/eventsystem/libinkevent.a \
-	$(top_builddir)/src/records/librecords_lm.a \
-	$(top_builddir)/proxy/shared/libdiagsconfig.a \
-   	$(LIBUNWIND_LIBS) \
-	@LIBPCRE@ @LIBCAP@ @HWLOC_LIBS@ \
-	@YAMLCPP_LIBS@
-	-lm
-
-# Must do it this way or the dependencies aren't detected.
-if BUILD_WCCP
-traffic_manager_traffic_manager_LDADD += \
-	wccp/libwccp.a \
-	@OPENSSL_LIBS@
-endif
diff --git a/src/traffic_manager/traffic_manager.cc b/src/traffic_manager/traffic_manager.cc
deleted file mode 100644
index f04c3df..0000000
--- a/src/traffic_manager/traffic_manager.cc
+++ /dev/null
@@ -1,1096 +0,0 @@
-/** @file
-
-  Entry point to the traffic manager.
-
-  @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 "tscore/ink_platform.h"
-#include "tscore/ink_sys_control.h"
-#include "tscore/ink_cap.h"
-#include "tscore/ink_lockfile.h"
-#include "tscore/ink_sock.h"
-#include "tscore/ink_args.h"
-#include "tscore/ink_syslog.h"
-#include "tscore/runroot.h"
-#include "tscore/Filenames.h"
-
-#include "WebMgmtUtils.h"
-#include "MgmtUtils.h"
-#include "MgmtSocket.h"
-#include "NetworkUtilsRemote.h"
-#include "FileManager.h"
-#include "tscore/I_Layout.h"
-#include "tscore/I_Version.h"
-#include "tscore/TextBuffer.h"
-#include "DiagsConfig.h"
-#include "HTTP.h"
-#include "CoreAPI.h"
-
-#include "LocalManager.h"
-#include "TSControlMain.h"
-#include "EventControlMain.h"
-
-// Needs LibRecordsConfigInit()
-#include "RecordsConfig.h"
-
-#include "records/P_RecLocal.h"
-#include "DerivativeMetrics.h"
-
-#include <random>
-
-#if TS_USE_POSIX_CAP
-#include <sys/capability.h>
-#endif
-#include <grp.h>
-#include <atomic>
-#include "tscore/bwf_std_format.h"
-
-#define FD_THROTTLE_HEADROOM (128 + 64) // TODO: consolidate with THROTTLE_FD_HEADROOM
-#define DEFAULT_DIAGS_LOG_FILENAME "manager.log"
-static char diags_log_filename[PATH_NAME_MAX] = DEFAULT_DIAGS_LOG_FILENAME;
-
-#if ATOMIC_INT_LOCK_FREE != 2
-#error "Need lock free std::atomic<int>"
-#endif
-
-using namespace std::literals;
-
-// These globals are still referenced directly by management API.
-LocalManager *lmgmt = nullptr;
-FileManager *configFiles;
-
-static void fileUpdated(char *fname, char *configName);
-static void runAsUser(const char *userName);
-
-#if defined(freebsd)
-extern "C" int getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, struct passwd **resptr);
-#endif
-
-static AppVersionInfo appVersionInfo; // Build info for this application
-
-static DiagsConfig *diagsConfig = nullptr;
-static char debug_tags[1024]    = "";
-static char action_tags[1024]   = "";
-static int proxy_off            = false;
-static int listen_off           = false;
-static char bind_stdout[512]    = "";
-static char bind_stderr[512]    = "";
-static const char *mgmt_path    = nullptr;
-
-// By default, set the current directory as base
-static const char *recs_conf = ts::filename::RECORDS;
-
-static int fds_limit;
-
-// TODO: Use positive instead negative selection
-//       This should just be #if defined(solaris)
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-static void SignalHandler(int sig, siginfo_t *t, void *f);
-static void SignalAlrmHandler(int sig, siginfo_t *t, void *f);
-#else
-static void SignalHandler(int sig);
-static void SignalAlrmHandler(int sig);
-#endif
-
-static std::atomic<int> sigHupNotifier;
-static void SigChldHandler(int sig);
-
-static void
-rotateLogs()
-{
-  // First, let us synchronously update the rolling config values for both diagslog
-  // and outputlog. Note that the config values for outputlog in traffic_server
-  // are never updated past the original instantiation of Diags. This shouldn't
-  // be an issue since we're never rolling outputlog from traffic_server anyways.
-  // The reason being is that it is difficult to send a notification from TS to
-  // TM, informing TM that outputlog has been rolled. It is much easier sending
-  // a notification (in the form of SIGUSR2) from TM -> TS.
-  int output_log_roll_int    = (int)REC_ConfigReadInteger("proxy.config.output.logfile.rolling_interval_sec");
-  int output_log_roll_size   = (int)REC_ConfigReadInteger("proxy.config.output.logfile.rolling_size_mb");
-  int output_log_roll_enable = (int)REC_ConfigReadInteger("proxy.config.output.logfile.rolling_enabled");
-  int diags_log_roll_int     = (int)REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_interval_sec");
-  int diags_log_roll_size    = (int)REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_size_mb");
-  int diags_log_roll_enable  = (int)REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_enabled");
-  diags()->config_roll_diagslog((RollingEnabledValues)diags_log_roll_enable, diags_log_roll_int, diags_log_roll_size);
-  diags()->config_roll_outputlog((RollingEnabledValues)output_log_roll_enable, output_log_roll_int, output_log_roll_size);
-
-  // Now we can actually roll the logs (if necessary)
-  if (diags()->should_roll_diagslog()) {
-    mgmt_log("Rotated %s", diags_log_filename);
-  }
-
-  if (diags()->should_roll_outputlog()) {
-    // send a signal to TS to reload traffic.out, so the logfile is kept
-    // synced across processes
-    mgmt_log("Sending SIGUSR2 to TS");
-    pid_t tspid = lmgmt->watched_process_pid;
-    if (tspid <= 0) {
-      return;
-    }
-    if (kill(tspid, SIGUSR2) != 0) {
-      mgmt_log("Could not send SIGUSR2 to TS: %s", strerror(errno));
-    } else {
-      mgmt_log("Successfully sent SIGUSR2 to TS!");
-    }
-  }
-}
-
-static bool
-is_server_idle()
-{
-  RecInt active    = 0;
-  RecInt threshold = 0;
-
-  if (RecGetRecordInt("proxy.config.restart.active_client_threshold", &threshold) != REC_ERR_OKAY) {
-    return false;
-  }
-
-  if (RecGetRecordInt("proxy.process.http.current_active_client_connections", &active) != REC_ERR_OKAY) {
-    return false;
-  }
-
-  Debug("lm", "%" PRId64 " active clients, threshold is %" PRId64, active, threshold);
-  return active <= threshold;
-}
-
-static bool
-is_server_idle_from_new_connection()
-{
-  RecInt active    = 0;
-  RecInt threshold = 0;
-
-  Debug("lm", "%" PRId64 " active clients, threshold is %" PRId64, active, threshold);
-
-#if 0
-  // TODO implement with the right metric
-  return active <= threshold;
-#else
-  return true;
-#endif
-}
-
-static bool
-is_server_draining()
-{
-  RecInt draining = 0;
-  if (RecGetRecordInt("proxy.node.config.draining", &draining) != REC_ERR_OKAY) {
-    return false;
-  }
-  return draining != 0;
-}
-
-static bool
-waited_enough()
-{
-  RecInt timeout = 0;
-  if (RecGetRecordInt("proxy.config.stop.shutdown_timeout", &timeout) != REC_ERR_OKAY) {
-    return false;
-  }
-
-  return (timeout ? (lmgmt->mgmt_shutdown_triggered_at + timeout <= time(nullptr)) : false);
-}
-
-static void
-check_lockfile()
-{
-  std::string rundir(RecConfigReadRuntimeDir());
-  char lockfile[PATH_NAME_MAX];
-  int err;
-  pid_t holding_pid;
-
-  //////////////////////////////////////
-  // test for presence of server lock //
-  //////////////////////////////////////
-  Layout::relative_to(lockfile, sizeof(lockfile), rundir, SERVER_LOCK);
-  Lockfile server_lockfile(lockfile);
-  err = server_lockfile.Open(&holding_pid);
-  if (err == 1) {
-    server_lockfile.Close(); // no server running
-  } else {
-    char *reason = strerror(-err);
-    if (err == 0) {
-      fprintf(stderr, "FATAL: Lockfile '%s' says server already running as PID %ld\n", lockfile, static_cast<long>(holding_pid));
-      mgmt_log("FATAL: Lockfile '%s' says server already running as PID %d\n", lockfile, holding_pid);
-    } else {
-      fprintf(stderr, "FATAL: Can't open server lockfile '%s' (%s)\n", lockfile, (reason ? reason : "Unknown Reason"));
-      mgmt_log("FATAL: Can't open server lockfile '%s' (%s)\n", lockfile, (reason ? reason : "Unknown Reason"));
-    }
-    exit(1);
-  }
-
-  ///////////////////////////////////////////
-  // try to get the exclusive manager lock //
-  ///////////////////////////////////////////
-  Layout::relative_to(lockfile, sizeof(lockfile), rundir, MANAGER_LOCK);
-  Lockfile manager_lockfile(lockfile);
-  err = manager_lockfile.Get(&holding_pid);
-  if (err != 1) {
-    char *reason = strerror(-err);
-    fprintf(stderr, "FATAL: Can't acquire manager lockfile '%s'", lockfile);
-    mgmt_log("FATAL: Can't acquire manager lockfile '%s'", lockfile);
-    if (err == 0) {
-      fprintf(stderr, " (Lock file held by process ID %ld)\n", static_cast<long>(holding_pid));
-      mgmt_log(" (Lock file held by process ID %d)\n", holding_pid);
-    } else if (reason) {
-      fprintf(stderr, " (%s)\n", reason);
-      mgmt_log(" (%s)\n", reason);
-    } else {
-      fprintf(stderr, "\n");
-    }
-    exit(1);
-
-    fprintf(stderr, "unable to acquire manager lock [%d]\n", -err);
-    exit(1);
-  }
-}
-
-static void
-initSignalHandlers()
-{
-  struct sigaction sigHandler, sigChldHandler, sigAlrmHandler;
-  sigset_t sigsToBlock;
-
-// Set up the signal handler
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-  sigHandler.sa_handler   = nullptr;
-  sigHandler.sa_sigaction = SignalHandler;
-#else
-  sigHandler.sa_handler     = SignalHandler;
-#endif
-  sigemptyset(&sigHandler.sa_mask);
-
-  // We want the handler to remain in place on
-  //  SIGHUP to avoid any races with the signals
-  //  coming too quickly.  Also restart systems calls
-  //  after the signal since not all calls are wrapped
-  //  to check errno for EINTR
-  sigHandler.sa_flags = SA_RESTART;
-  sigaction(SIGHUP, &sigHandler, nullptr);
-  sigaction(SIGUSR2, &sigHandler, nullptr);
-
-// Don't block the signal on entry to the signal
-//   handler so we can reissue it and get a core
-//   file in the appropriate circumstances
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-  sigHandler.sa_flags = SA_RESETHAND | SA_SIGINFO;
-#else
-  sigHandler.sa_flags       = SA_RESETHAND;
-#endif
-  sigaction(SIGINT, &sigHandler, nullptr);
-  sigaction(SIGQUIT, &sigHandler, nullptr);
-  sigaction(SIGILL, &sigHandler, nullptr);
-  sigaction(SIGBUS, &sigHandler, nullptr);
-  sigaction(SIGSEGV, &sigHandler, nullptr);
-  sigaction(SIGTERM, &sigHandler, nullptr);
-
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-  sigAlrmHandler.sa_handler   = nullptr;
-  sigAlrmHandler.sa_sigaction = SignalAlrmHandler;
-#else
-  sigAlrmHandler.sa_handler = SignalAlrmHandler;
-#endif
-
-  sigemptyset(&sigAlrmHandler.sa_mask);
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-  sigAlrmHandler.sa_flags = SA_SIGINFO;
-#else
-  sigAlrmHandler.sa_flags   = 0;
-#endif
-  sigaction(SIGALRM, &sigAlrmHandler, nullptr);
-
-  // Block the delivery of any signals we are not catching
-  //
-  //  except for SIGALRM since we use it
-  //    to break out of deadlock on semaphore
-  //    we share with the proxy
-  //
-  sigfillset(&sigsToBlock);
-  sigdelset(&sigsToBlock, SIGHUP);
-  sigdelset(&sigsToBlock, SIGUSR2);
-  sigdelset(&sigsToBlock, SIGINT);
-  sigdelset(&sigsToBlock, SIGQUIT);
-  sigdelset(&sigsToBlock, SIGILL);
-  sigdelset(&sigsToBlock, SIGABRT);
-  sigdelset(&sigsToBlock, SIGBUS);
-  sigdelset(&sigsToBlock, SIGSEGV);
-  sigdelset(&sigsToBlock, SIGTERM);
-  sigdelset(&sigsToBlock, SIGALRM);
-  ink_thread_sigsetmask(SIG_SETMASK, &sigsToBlock, nullptr);
-
-  // Set up the SIGCHLD handler so we do not get into
-  //   a problem with Solaris 2.6 and strange waitpid()
-  //   behavior
-  sigChldHandler.sa_handler = SigChldHandler;
-  sigChldHandler.sa_flags   = SA_RESTART;
-  sigemptyset(&sigChldHandler.sa_mask);
-  sigaction(SIGCHLD, &sigChldHandler, nullptr);
-}
-
-static void
-init_dirs()
-{
-  std::string rundir(RecConfigReadRuntimeDir());
-  std::string sysconfdir(RecConfigReadConfigDir());
-
-  if (access(sysconfdir.c_str(), R_OK) == -1) {
-    mgmt_elog(0, "unable to access() config directory '%s': %d, %s\n", sysconfdir.c_str(), errno, strerror(errno));
-    mgmt_elog(0, "please set the 'TS_ROOT' environment variable\n");
-    ::exit(1);
-  }
-
-  if (access(rundir.c_str(), R_OK) == -1) {
-    mgmt_elog(0, "unable to access() local state directory '%s': %d, %s\n", rundir.c_str(), errno, strerror(errno));
-    mgmt_elog(0, "please set 'proxy.config.local_state_dir'\n");
-    ::exit(1);
-  }
-}
-
-static void
-chdir_root()
-{
-  std::string prefix = Layout::get()->prefix;
-
-  if (chdir(prefix.c_str()) < 0) {
-    mgmt_elog(0, "unable to change to root directory \"%s\" [%d '%s']\n", prefix.c_str(), errno, strerror(errno));
-    mgmt_elog(0, " please set correct path in env variable TS_ROOT \n");
-    exit(1);
-  } else {
-    mgmt_log("[TrafficManager] using root directory '%s'\n", prefix.c_str());
-  }
-}
-
-static void
-set_process_limits(RecInt fds_throttle)
-{
-  struct rlimit lim;
-  rlim_t maxfiles;
-
-  // Set needed rlimits (root)
-  ink_max_out_rlimit(RLIMIT_NOFILE);
-  ink_max_out_rlimit(RLIMIT_STACK);
-  ink_max_out_rlimit(RLIMIT_DATA);
-  ink_max_out_rlimit(RLIMIT_FSIZE);
-#ifdef RLIMIT_RSS
-  ink_max_out_rlimit(RLIMIT_RSS);
-#endif
-
-  maxfiles = ink_get_max_files();
-  if (maxfiles != RLIM_INFINITY) {
-    float file_max_pct = 0.9;
-
-    REC_ReadConfigFloat(file_max_pct, "proxy.config.system.file_max_pct");
-    if (file_max_pct > 1.0) {
-      file_max_pct = 1.0;
-    }
-
-    lim.rlim_cur = lim.rlim_max = static_cast<rlim_t>(maxfiles * file_max_pct);
-    if (setrlimit(RLIMIT_NOFILE, &lim) == 0 && getrlimit(RLIMIT_NOFILE, &lim) == 0) {
-      fds_limit = static_cast<int>(lim.rlim_cur);
-      syslog(LOG_NOTICE, "NOTE: RLIMIT_NOFILE(%d):cur(%d),max(%d)", RLIMIT_NOFILE, static_cast<int>(lim.rlim_cur),
-             static_cast<int>(lim.rlim_max));
-    }
-  }
-
-  if (getrlimit(RLIMIT_NOFILE, &lim) == 0) {
-    if (fds_throttle > (int)(lim.rlim_cur + FD_THROTTLE_HEADROOM)) {
-      lim.rlim_cur = (lim.rlim_max = (rlim_t)fds_throttle);
-      if (!setrlimit(RLIMIT_NOFILE, &lim) && !getrlimit(RLIMIT_NOFILE, &lim)) {
-        fds_limit = static_cast<int>(lim.rlim_cur);
-        syslog(LOG_NOTICE, "NOTE: RLIMIT_NOFILE(%d):cur(%d),max(%d)", RLIMIT_NOFILE, static_cast<int>(lim.rlim_cur),
-               static_cast<int>(lim.rlim_max));
-      }
-    }
-  }
-}
-
-#if TS_HAS_WCCP
-static void
-Errata_Logger(ts::Errata const &err)
-{
-  size_t n;
-  static size_t const SIZE = 4096;
-  char buff[SIZE];
-  if (err.size()) {
-    ts::Errata::Code code = err.top().getCode();
-    n                     = err.write(buff, SIZE, 1, 0, 2, "> ");
-    // strip trailing newlines.
-    while (n && (buff[n - 1] == '\n' || buff[n - 1] == '\r'))
-      buff[--n] = 0;
-    // log it.
-    if (code > 1)
-      mgmt_elog(0, "[WCCP]%s", buff);
-    else if (code > 0)
-      mgmt_log("[WCCP]%s", buff);
-    else
-      Debug("WCCP", "%s", buff);
-  }
-}
-
-static void
-Init_Errata_Logging()
-{
-  ts::Errata::registerSink(&Errata_Logger);
-}
-#endif
-
-static void
-millisleep(int ms)
-{
-  struct timespec ts;
-
-  ts.tv_sec  = ms / 1000;
-  ts.tv_nsec = (ms - ts.tv_sec * 1000) * 1000 * 1000;
-  nanosleep(&ts, nullptr); // we use nanosleep instead of sleep because it does not interact with signals
-}
-
-bool
-api_socket_is_restricted()
-{
-  RecInt intval;
-
-  // If the socket is not administratively restricted, check whether we have platform
-  // support. Otherwise, default to making it restricted.
-  if (RecGetRecordInt("proxy.config.admin.api.restricted", &intval) == REC_ERR_OKAY) {
-    if (intval == 0) {
-      return !mgmt_has_peereid();
-    }
-  }
-
-  return true;
-}
-
-int
-main(int argc, const char **argv)
-{
-  const long MAX_LOGIN = ink_login_name_max();
-
-  runroot_handler(argv);
-
-  // Before accessing file system initialize Layout engine
-  Layout::create();
-  mgmt_path = Layout::get()->sysconfdir.c_str();
-
-  // Set up the application version info
-  appVersionInfo.setup(PACKAGE_NAME, "traffic_manager", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
-
-  bool found       = false;
-  int just_started = 0;
-  // TODO: This seems completely incomplete, disabled for now
-  //  int dump_config = 0, dump_process = 0, dump_node = 0, dump_local = 0;
-  char *proxy_port   = nullptr;
-  char *tsArgs       = nullptr;
-  int disable_syslog = false;
-  char userToRunAs[MAX_LOGIN + 1];
-  RecInt fds_throttle        = -1;
-  bool printed_unrecoverable = false;
-
-  ArgumentDescription argument_descriptions[] = {
-    {"proxyOff", '-', "Disable proxy", "F", &proxy_off, nullptr, nullptr},
-    {"listenOff", '-', "Disable traffic manager listen to proxy ports", "F", &listen_off, nullptr, nullptr},
-    {"path", '-', "Path to the management socket", "S*", &mgmt_path, nullptr, nullptr},
-    {"recordsConf", '-', "Path to records.config", "S*", &recs_conf, nullptr, nullptr},
-    {"tsArgs", '-', "Additional arguments for traffic_server", "S*", &tsArgs, nullptr, nullptr},
-    {"proxyPort", '-', "HTTP port descriptor", "S*", &proxy_port, nullptr, nullptr},
-    {"maxRecords", 'm', "Max number of librecords metrics and configurations (default & minimum: 1600)", "I", &max_records_entries,
-     "PROXY_MAX_RECORDS", nullptr},
-    {TM_OPT_BIND_STDOUT, '-', "Regular file to bind stdout to", "S512", &bind_stdout, "PROXY_BIND_STDOUT", nullptr},
-    {TM_OPT_BIND_STDERR, '-', "Regular file to bind stderr to", "S512", &bind_stderr, "PROXY_BIND_STDERR", nullptr},
-#if TS_USE_DIAGS
-    {"debug", 'T', "Vertical-bar-separated Debug Tags", "S1023", debug_tags, nullptr, nullptr},
-    {"action", 'B', "Vertical-bar-separated Behavior Tags", "S1023", action_tags, nullptr, nullptr},
-#endif
-    {"nosyslog", '-', "Do not log to syslog", "F", &disable_syslog, nullptr, nullptr},
-    HELP_ARGUMENT_DESCRIPTION(),
-    VERSION_ARGUMENT_DESCRIPTION(),
-    RUNROOT_ARGUMENT_DESCRIPTION()
-  };
-
-  // Process command line arguments and dump into variables
-  process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv);
-
-  // change the directory to the "root" directory
-  chdir_root();
-
-  // Line buffer standard output & standard error
-  int status;
-  status = setvbuf(stdout, nullptr, _IOLBF, 0);
-  if (status != 0) {
-    perror("WARNING: can't line buffer stdout");
-  }
-  status = setvbuf(stderr, nullptr, _IOLBF, 0);
-  if (status != 0) {
-    perror("WARNING: can't line buffer stderr");
-  }
-
-  initSignalHandlers();
-
-  // Bootstrap with LOG_DAEMON until we've read our configuration
-  if (!disable_syslog) {
-    openlog("traffic_manager", LOG_PID | LOG_NDELAY | LOG_NOWAIT, LOG_DAEMON);
-    mgmt_use_syslog();
-    syslog(LOG_NOTICE, "NOTE: --- Manager Starting ---");
-    syslog(LOG_NOTICE, "NOTE: Manager Version: %s", appVersionInfo.FullVersionInfoStr);
-  }
-
-  // Bootstrap the Diags facility so that we can use it while starting
-  //  up the manager
-  diagsConfig = new DiagsConfig("Manager", DEFAULT_DIAGS_LOG_FILENAME, debug_tags, action_tags, false);
-  diags()->set_std_output(StdStream::STDOUT, bind_stdout);
-  diags()->set_std_output(StdStream::STDERR, bind_stderr);
-
-  RecLocalInit();
-  LibRecordsConfigInit();
-
-  init_dirs(); // setup critical directories, needs LibRecords
-
-  if (RecGetRecordString("proxy.config.admin.user_id", userToRunAs, sizeof(userToRunAs)) != REC_ERR_OKAY ||
-      strlen(userToRunAs) == 0) {
-    mgmt_fatal(0, "proxy.config.admin.user_id is not set\n");
-  }
-
-  RecGetRecordInt("proxy.config.net.connections_throttle", &fds_throttle);
-  RecInt listen_per_thread = 0;
-  RecGetRecordInt("proxy.config.exec_thread.listen", &listen_per_thread);
-  if (listen_per_thread > 0) { // Turn off listening. Traffic server is going to listen on all the threads.
-    listen_off = true;
-  }
-
-  set_process_limits(fds_throttle); // as root
-
-  // A user of #-1 means to not attempt to switch user. Yes, it's documented ;)
-  if (strcmp(userToRunAs, "#-1") != 0) {
-    runAsUser(userToRunAs);
-  }
-
-  EnableCoreFile(true);
-  check_lockfile();
-
-  url_init();
-  mime_init();
-  http_init();
-
-#if TS_HAS_WCCP
-  Init_Errata_Logging();
-#endif
-  ts_host_res_global_init();
-  ts_session_protocol_well_known_name_indices_init();
-  lmgmt = new LocalManager(proxy_off == false, listen_off == false);
-  RecLocalInitMessage();
-  lmgmt->initAlarm();
-
-  // INKqa11968: need to set up callbacks and diags data structures
-  // using configuration in records.config
-  REC_ReadConfigString(diags_log_filename, "proxy.node.config.manager_log_filename", sizeof(diags_log_filename));
-  if (strnlen(diags_log_filename, sizeof(diags_log_filename)) == 0) {
-    strncpy(diags_log_filename, DEFAULT_DIAGS_LOG_FILENAME, sizeof(diags_log_filename));
-  }
-  DiagsConfig *old_diagsconfig = diagsConfig;
-  diagsConfig                  = new DiagsConfig("Manager", diags_log_filename, debug_tags, action_tags, true);
-  if (old_diagsconfig) {
-    delete old_diagsconfig;
-    old_diagsconfig = nullptr;
-  }
-
-  RecSetDiags(diags());
-  diags()->set_std_output(StdStream::STDOUT, bind_stdout);
-  diags()->set_std_output(StdStream::STDERR, bind_stderr);
-
-  if (is_debug_tag_set("diags")) {
-    diags()->dump();
-  }
-  diags()->cleanup_func = mgmt_cleanup;
-
-  // Setup the exported manager version records.
-  RecSetRecordString("proxy.node.version.manager.short", appVersionInfo.VersionStr, REC_SOURCE_DEFAULT);
-  RecSetRecordString("proxy.node.version.manager.long", appVersionInfo.FullVersionInfoStr, REC_SOURCE_DEFAULT);
-  RecSetRecordString("proxy.node.version.manager.build_number", appVersionInfo.BldNumStr, REC_SOURCE_DEFAULT);
-  RecSetRecordString("proxy.node.version.manager.build_time", appVersionInfo.BldTimeStr, REC_SOURCE_DEFAULT);
-  RecSetRecordString("proxy.node.version.manager.build_date", appVersionInfo.BldDateStr, REC_SOURCE_DEFAULT);
-  RecSetRecordString("proxy.node.version.manager.build_machine", appVersionInfo.BldMachineStr, REC_SOURCE_DEFAULT);
-  RecSetRecordString("proxy.node.version.manager.build_person", appVersionInfo.BldPersonStr, REC_SOURCE_DEFAULT);
-
-  if (!disable_syslog) {
-    char sys_var[]     = "proxy.config.syslog_facility";
-    char *facility_str = nullptr;
-    int facility_int;
-
-    facility_str = REC_readString(sys_var, &found);
-    ink_assert(found);
-
-    if (!found) {
-      mgmt_elog(0, "Could not read %s.  Defaulting to LOG_DAEMON\n", sys_var);
-      facility_int = LOG_DAEMON;
-    } else {
-      facility_int = facility_string_to_int(facility_str);
-      ats_free(facility_str);
-      if (facility_int < 0) {
-        mgmt_elog(0, "Bad syslog facility specified.  Defaulting to LOG_DAEMON\n");
-        facility_int = LOG_DAEMON;
-      }
-    }
-
-    // NOTE: do NOT call closelog() here.  Solaris gets confused.
-    openlog("traffic_manager", LOG_PID | LOG_NDELAY | LOG_NOWAIT, facility_int);
-
-    lmgmt->syslog_facility = facility_int;
-  } else {
-    lmgmt->syslog_facility = -1;
-  }
-
-  // Find out our hostname so we can use it as part of the initialization
-  setHostnameVar();
-
-  // Initialize the Config Object bindings before
-  //   starting any other threads
-  lmgmt->configFiles = configFiles = new FileManager();
-  initializeRegistry();
-  configFiles->registerCallback(fileUpdated);
-
-  // RecLocal's 'sync_thr' depends on 'configFiles', so we can't
-  // stat the 'sync_thr' until 'configFiles' has been initialized.
-  RecLocalStart(configFiles);
-
-  // TS needs to be started up with the same outputlog bindings each time,
-  // so we append the outputlog location to the persistent proxy options
-  //
-  // TS needs them to be able to create BaseLogFiles for each value
-  ts::bwprint(lmgmt->proxy_options, "{}{}{}", ts::bwf::OptionalAffix(tsArgs),
-              ts::bwf::OptionalAffix(bind_stdout, " "sv, "--bind_stdout "sv),
-              ts::bwf::OptionalAffix(bind_stderr, " "sv, "--bind_stderr "sv));
-
-  if (proxy_port) {
-    HttpProxyPort::loadValue(lmgmt->m_proxy_ports, proxy_port);
-  }
-
-  lmgmt->initMgmtProcessServer(); /* Setup p-to-p process server */
-
-  lmgmt->listenForProxy();
-
-  // Setup the API and event sockets
-  std::string rundir(RecConfigReadRuntimeDir());
-  std::string apisock(Layout::relative_to(rundir, MGMTAPI_MGMT_SOCKET_NAME));
-  std::string eventsock(Layout::relative_to(rundir, MGMTAPI_EVENT_SOCKET_NAME));
-
-  Debug("lm", "using main socket file '%s'", apisock.c_str());
-  Debug("lm", "using event socket file '%s'", eventsock.c_str());
-
-  mode_t oldmask = umask(0);
-  mode_t newmode = api_socket_is_restricted() ? 00700 : 00777;
-
-  int mgmtapiFD  = -1; // FD for the api interface to issue commands
-  int eventapiFD = -1; // FD for the api and clients to handle event callbacks
-
-  mgmtapiFD = bind_unix_domain_socket(apisock.c_str(), newmode);
-  if (mgmtapiFD == -1) {
-    mgmt_log("[WebIntrMain] Unable to set up socket for handling management API calls. API socket path = %s\n", apisock.c_str());
-  }
-
-  eventapiFD = bind_unix_domain_socket(eventsock.c_str(), newmode);
-  if (eventapiFD == -1) {
-    mgmt_log("[WebIntrMain] Unable to set up so for handling management API event calls. Event Socket path: %s\n",
-             eventsock.c_str());
-  }
-
-  umask(oldmask);
-  ink_thread_create(nullptr, ts_ctrl_main, &mgmtapiFD, 0, 0, nullptr);
-  ink_thread_create(nullptr, event_callback_main, &eventapiFD, 0, 0, nullptr);
-
-  mgmt_log("[TrafficManager] Setup complete\n");
-
-  RecRegisterStatInt(RECT_NODE, "proxy.node.config.reconfigure_time", time(nullptr), RECP_NON_PERSISTENT);
-  RecRegisterStatInt(RECT_NODE, "proxy.node.config.reconfigure_required", 0, RECP_NON_PERSISTENT);
-
-  RecRegisterStatInt(RECT_NODE, "proxy.node.config.restart_required.proxy", 0, RECP_NON_PERSISTENT);
-  RecRegisterStatInt(RECT_NODE, "proxy.node.config.restart_required.manager", 0, RECP_NON_PERSISTENT);
-
-  RecRegisterStatInt(RECT_NODE, "proxy.node.config.draining", 0, RECP_NON_PERSISTENT);
-
-  int sleep_time             = 0; // sleep_time given in sec
-  uint64_t last_start_epoc_s = 0; // latest start attempt in seconds since epoc
-
-  std::random_device rd;
-  std::mt19937 gen(rd());
-  std::uniform_real_distribution<> dis(0.0, 0.5);
-
-  RecInt sleep_ceiling = 60;
-  RecGetRecordInt("proxy.node.config.manager_exponential_sleep_ceiling", &sleep_ceiling);
-  RecInt retry_cap = 0; // 0  means no cap.
-  RecGetRecordInt("proxy.node.config.manager_retry_cap", &retry_cap);
-  bool ignore_retry_cap{retry_cap <= 0};
-
-  DerivativeMetrics derived; // This is simple class to calculate some useful derived metrics
-
-  for (;;) {
-    lmgmt->processEventQueue();
-    lmgmt->pollMgmtProcessServer();
-
-    // Handle rotation of output log (aka traffic.out) as well as DEFAULT_DIAGS_LOG_FILENAME (aka manager.log)
-    rotateLogs();
-
-    // Check for a SIGHUP
-    if (sigHupNotifier) {
-      mgmt_log("[main] Reading Configuration Files due to SIGHUP\n");
-      Reconfigure();
-      sigHupNotifier = 0;
-      mgmt_log("[main] Reading Configuration Files Reread\n");
-    }
-
-    // Update the derived metrics. ToDo: this runs once a second, that might be excessive, maybe it should be
-    // done more like every config_update_interval_ms (proxy.config.config_update_interval_ms) ?
-    derived.Update();
-
-    if (lmgmt->mgmt_shutdown_outstanding != MGMT_PENDING_NONE) {
-      Debug("lm", "pending shutdown %d", lmgmt->mgmt_shutdown_outstanding);
-    }
-    switch (lmgmt->mgmt_shutdown_outstanding) {
-    case MGMT_PENDING_RESTART:
-      lmgmt->mgmtShutdown();
-      ::exit(0);
-      break;
-    case MGMT_PENDING_IDLE_RESTART:
-      if (!is_server_draining()) {
-        lmgmt->processDrain();
-      }
-      if (is_server_idle() || waited_enough()) {
-        lmgmt->mgmtShutdown();
-        ::exit(0);
-      }
-      break;
-    case MGMT_PENDING_BOUNCE:
-      lmgmt->processBounce();
-      lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      break;
-    case MGMT_PENDING_IDLE_BOUNCE:
-      if (!is_server_draining()) {
-        lmgmt->processDrain();
-      }
-      if (is_server_idle() || waited_enough()) {
-        lmgmt->processBounce();
-        lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      }
-      break;
-    case MGMT_PENDING_STOP:
-      lmgmt->processShutdown();
-      lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      break;
-    case MGMT_PENDING_IDLE_STOP:
-      if (!is_server_draining()) {
-        lmgmt->processDrain();
-      }
-      if (is_server_idle() || waited_enough()) {
-        lmgmt->processShutdown();
-        lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      }
-      break;
-    case MGMT_PENDING_DRAIN:
-      if (!is_server_draining()) {
-        lmgmt->processDrain();
-      }
-      lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      break;
-    case MGMT_PENDING_IDLE_DRAIN:
-      if (is_server_idle_from_new_connection()) {
-        lmgmt->processDrain();
-        lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      }
-      break;
-    case MGMT_PENDING_UNDO_DRAIN:
-      if (is_server_draining()) {
-        lmgmt->processDrain(0);
-        lmgmt->mgmt_shutdown_outstanding = MGMT_PENDING_NONE;
-      }
-      break;
-    default:
-      break;
-    }
-
-    if (lmgmt->run_proxy && !lmgmt->processRunning() && lmgmt->proxy_recoverable &&
-        (retry_cap > 0 || ignore_retry_cap)) { /* Make sure we still have a proxy up */
-      const uint64_t now = static_cast<uint64_t>(time(nullptr));
-      if (sleep_time && ((now - last_start_epoc_s) < static_cast<uint64_t>(sleep_ceiling))) {
-        const auto variance = dis(gen);
-        // We add a bit of variance to the regular sleep time.
-        const int mod_sleep_time = sleep_time + static_cast<int>(sleep_time * variance);
-        mgmt_log("Relaunching proxy after %d sec..", mod_sleep_time);
-        if (!ignore_retry_cap && sleep_time >= sleep_ceiling) {
-          --retry_cap;
-        }
-        millisleep((1000 * mod_sleep_time)); // we use millisleep instead of sleep because it doesnt interfere with signals
-        sleep_time = std::min<int>(sleep_time * 2, sleep_ceiling);
-      } else {
-        sleep_time = 1;
-      }
-      if (ProxyStateSet(TS_PROXY_ON, TS_CACHE_CLEAR_NONE) == TS_ERR_OKAY) {
-        just_started      = 0;
-        last_start_epoc_s = static_cast<uint64_t>(time(nullptr));
-      } else {
-        just_started++;
-      }
-    } else {
-      // Even if we shouldn't try to start the proxy again, leave manager around to
-      // avoid external automated restarts
-      if (!lmgmt->proxy_recoverable && !printed_unrecoverable) {
-        mgmt_log("[main] Proxy is un-recoverable. Proxy will not be relaunched.\n");
-        printed_unrecoverable = true;
-      }
-
-      just_started++;
-    }
-
-    /* This will catch the case were the proxy dies before it can connect to manager */
-    if (lmgmt->proxy_launch_outstanding && !lmgmt->processRunning() && just_started >= 120) {
-      just_started                    = 0;
-      lmgmt->proxy_launch_outstanding = false;
-      if (lmgmt->proxy_launch_pid != -1) {
-        int res;
-        kill(lmgmt->proxy_launch_pid, 9);
-        waitpid(lmgmt->proxy_launch_pid, &res, 0);
-        if (WIFSIGNALED(res)) {
-          int sig = WTERMSIG(res);
-#ifdef NEED_PSIGNAL
-          mgmt_log("[main] Proxy terminated due to Sig %d. Relaunching after %d sec...\n", sig, sleep_time);
-#else
-          mgmt_log("[main] Proxy terminated due to Sig %d: %s. Relaunching after %d sec...\n", sig, strsignal(sig), sleep_time);
-#endif /* NEED_PSIGNAL */
-        }
-      }
-      mgmt_log("[main] Proxy launch failed, retrying after %d sec...\n", sleep_time);
-    }
-  }
-
-  // ToDo: Here we should delete anything related to calculated metrics.
-
-#ifndef MGMT_SERVICE
-  return 0;
-#endif
-
-} /* End main */
-
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-static void
-SignalAlrmHandler(int /* sig ATS_UNUSED */, siginfo_t *t, void * /* c ATS_UNUSED */)
-#else
-static void
-SignalAlrmHandler(int /* sig ATS_UNUSED */)
-#endif
-{
-/*
-   fprintf("[TrafficManager] ==> SIGALRM received\n");
-   mgmt_elog(0, "[TrafficManager] ==> SIGALRM received\n");
- */
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-  if (t) {
-    if (t->si_code <= 0) {
-      fprintf(stderr, "[TrafficManager] ==> User Alarm from pid: %ld uid: %d\n", (long)t->si_pid, t->si_uid);
-      mgmt_log("[TrafficManager] ==> User Alarm from pid: %d uid: %d\n", t->si_pid, t->si_uid);
-    } else {
-      fprintf(stderr, "[TrafficManager] ==> Kernel Alarm Reason: %d\n", t->si_code);
-      mgmt_log("[TrafficManager] ==> Kernel Alarm Reason: %d\n", t->si_code);
-    }
-  }
-#endif
-
-  return;
-}
-
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-static void
-SignalHandler(int sig, siginfo_t *t, void *c)
-#else
-static void
-SignalHandler(int sig)
-#endif
-{
-  static int clean = 0;
-  int status;
-
-#if !defined(linux) && !defined(freebsd) && !defined(darwin)
-  if (t) {
-    if (t->si_code <= 0) {
-      fprintf(stderr, "[TrafficManager] ==> User Sig %d from pid: %ld uid: %d\n", sig, (long)t->si_pid, t->si_uid);
-      mgmt_log("[TrafficManager] ==> User Sig %d from pid: %ld uid: %d\n", sig, (long)t->si_pid, t->si_uid);
-    } else {
-      fprintf(stderr, "[TrafficManager] ==> Kernel Sig %d; Reason: %d\n", sig, t->si_code);
-      mgmt_log("[TrafficManager] ==> Kernel Sig %d; Reason: %d\n", sig, t->si_code);
-    }
-  }
-#endif
-
-  if (sig == SIGHUP) {
-    sigHupNotifier = 1;
-    return;
-  }
-
-  if (sig == SIGUSR2) {
-    fprintf(stderr, "[TrafficManager] ==> received SIGUSR2, rotating the logs.\n");
-    mgmt_log("[TrafficManager] ==> received SIGUSR2, rotating the logs.\n");
-    if (lmgmt && lmgmt->watched_process_pid != -1) {
-      kill(lmgmt->watched_process_pid, sig);
-    }
-    diags()->set_std_output(StdStream::STDOUT, bind_stdout);
-    diags()->set_std_output(StdStream::STDERR, bind_stderr);
-    if (diags()->reseat_diagslog()) {
-      Note("Reseated %s", diags_log_filename);
-    } else {
-      Note("Could not reseat %s", diags_log_filename);
-    }
-    return;
-  }
-
-  fprintf(stderr, "[TrafficManager] ==> Cleaning up and reissuing signal #%d\n", sig);
-  mgmt_log("[TrafficManager] ==> Cleaning up and reissuing signal #%d\n", sig);
-
-  if (lmgmt && !clean) {
-    clean = 1;
-    if (lmgmt->watched_process_pid != -1) {
-      if (sig == SIGTERM || sig == SIGINT) {
-        kill(lmgmt->watched_process_pid, sig);
-        waitpid(lmgmt->watched_process_pid, &status, 0);
-      }
-    }
-    lmgmt->mgmtCleanup();
-  }
-
-  switch (sig) {
-  case SIGQUIT:
-  case SIGILL:
-  case SIGTRAP:
-#if !defined(linux)
-  case SIGEMT:
-  case SIGSYS:
-#endif
-  case SIGFPE:
-  case SIGBUS:
-  case SIGSEGV:
-  case SIGXCPU:
-  case SIGXFSZ:
-    abort();
-  case SIGTERM:
-    ::exit(0);
-  default:
-    fprintf(stderr, "[TrafficManager] ==> signal #%d\n", sig);
-    mgmt_log("[TrafficManager] ==> signal #%d\n", sig);
-    ::exit(sig);
-  }
-  fprintf(stderr, "[TrafficManager] ==> signal2 #%d\n", sig);
-  mgmt_log("[TrafficManager] ==> signal2 #%d\n", sig);
-  ::exit(sig);
-} /* End SignalHandler */
-
-// void SigChldHandler(int sig)
-//
-//   An empty handler needed so that we catch SIGCHLD
-//    With Solaris 2.6, ignoring sig child changes the behavior
-//    of waitpid() so that if there are no unwaited children,
-//    waitpid() blocks until all child are transformed into
-//    zombies which is bad for us
-//
-static void
-SigChldHandler(int /* sig ATS_UNUSED */)
-{
-}
-
-void
-fileUpdated(char *fname, char *configName)
-{
-  // If there is no config name recorded, assume this file is not reloadable
-  // Just log a message
-  if (configName == nullptr || configName[0] == '\0') {
-    mgmt_log("[fileUpdated] %s changed, need restart", fname);
-  } else {
-    // Signal based on the config entry that has the changed file name
-    lmgmt->signalFileChange(configName);
-  }
-  return;
-} /* End fileUpdate */
-
-#if TS_USE_POSIX_CAP
-/** Restore capabilities after user id change.
-    This manipulates LINUX capabilities so that this process
-    can perform certain privileged operations even if it is
-    no longer running as a privilege user.
-
-    @internal
-    I tried using
-    @code
-    prctl(PR_SET_KEEPCAPS, 1);
-    @endcode
-    but that had no effect even though the call reported success.
-    Only explicit capability manipulation was effective.
-
-    It does not appear to be necessary to set the capabilities on the
-    executable if originally run as root. That may be needed if
-    started as a user without that capability.
- */
-
-int
-restoreCapabilities()
-{
-  int zret      = 0;              // return value.
-  cap_t cap_set = cap_get_proc(); // current capabilities
-  // Make a list of the capabilities we want turned on.
-  cap_value_t cap_list[] = {
-    CAP_NET_ADMIN,        ///< Set socket transparency.
-    CAP_NET_BIND_SERVICE, ///< Low port (e.g. 80) binding.
-    CAP_IPC_LOCK          ///< Lock IPC objects.
-  };
-  static int const CAP_COUNT = sizeof(cap_list) / sizeof(*cap_list);
-
-  for (int i = 0; i < CAP_COUNT; i++) {
-    if (cap_set_flag(cap_set, CAP_EFFECTIVE, 1, cap_list + i, CAP_SET) < 0) {
-      Warning("restore CAP_EFFECTIVE failed for option %d", i);
-    }
-    if (cap_set_proc(cap_set) == -1) { // it failed, back out
-      cap_set_flag(cap_set, CAP_EFFECTIVE, 1, cap_list + i, CAP_CLEAR);
-    }
-  }
-  for (int i : cap_list) {
-    cap_flag_value_t val;
-    if (cap_get_flag(cap_set, i, CAP_EFFECTIVE, &val) < 0) {
-    } else {
-      Warning("CAP_EFFECTIVE offiset %d is %s", i, val == CAP_SET ? "set" : "unset");
-    }
-  }
-  zret = cap_set_proc(cap_set);
-  cap_free(cap_set);
-  return zret;
-}
-#endif
-
-//  void runAsUser(...)
-//
-//  If we are root, switched to user to run as
-//    specified in records.config
-//
-//  If we are not root, do nothing
-//
-void
-runAsUser(const char *userName)
-{
-  if (getuid() == 0 || geteuid() == 0) {
-    ImpersonateUser(userName, IMPERSONATE_EFFECTIVE);
-
-#if TS_USE_POSIX_CAP
-    if (0 != restoreCapabilities()) {
-      mgmt_log("[runAsUser] Error: Failed to restore capabilities after switch to user %s.\n", userName);
-    }
-#endif
-  }
-} /* End runAsUser() */
diff --git a/src/traffic_quic/Makefile.inc b/src/traffic_quic/Makefile.inc
index 239a318..09145e6 100644
--- a/src/traffic_quic/Makefile.inc
+++ b/src/traffic_quic/Makefile.inc
@@ -32,11 +32,12 @@
 	-I$(abs_top_srcdir)/proxy/http3 \
 	-I$(abs_top_srcdir)/proxy/logging \
 	-I$(abs_top_srcdir)/proxy/shared \
-	$(TS_INCLUDES) \
+	$(TS_INCLUDES) @SWOC_INCLUDES@ \
 	@OPENSSL_INCLUDES@ @YAMLCPP_INCLUDES@
 
 traffic_quic_traffic_quic_LDFLAGS = \
 	$(AM_LDFLAGS) \
+	@SWOC_LIBS@ \
 	@OPENSSL_LDFLAGS@ \
 	@BORINGOCSP_LDFLAGS@
 
@@ -59,8 +60,13 @@
 	$(top_builddir)/proxy/libproxy.a \
 	$(top_builddir)/proxy/hdrs/libhdrs.a \
 	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ \
 	@YAMLCPP_LIBS@ \
 	@OPENSSL_LIBS@ \
 	@BORINGOCSP_LIBS@ \
 	@LIBPCRE@
 
+if USE_QUICHE
+traffic_quic_traffic_quic_LDADD += \
+  $(QUICHE_LIB)
+endif
diff --git a/src/traffic_quic/quic_client.cc b/src/traffic_quic/quic_client.cc
index 8b3301a..dbdfa89 100644
--- a/src/traffic_quic/quic_client.cc
+++ b/src/traffic_quic/quic_client.cc
@@ -29,6 +29,7 @@
 
 #include "Http3Transaction.h"
 #include "P_QUICNetVConnection.h"
+#include "quic/QUICStreamManager.h"
 
 // OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
 // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
diff --git a/src/traffic_quic/traffic_quic.cc b/src/traffic_quic/traffic_quic.cc
index d4dcecb..c54bfa0 100644
--- a/src/traffic_quic/traffic_quic.cc
+++ b/src/traffic_quic/traffic_quic.cc
@@ -111,7 +111,7 @@
   http_init();
   hpack_huffman_init();
 
-  Http3Config::startup();
+  ts::Http3Config::startup();
 
   QUICClient client(&config);
   eventProcessor.schedule_in(&client, 1, ET_NET);
diff --git a/src/traffic_server/EventName.cc b/src/traffic_server/EventName.cc
index ae0c5993..a062c4a 100644
--- a/src/traffic_server/EventName.cc
+++ b/src/traffic_server/EventName.cc
@@ -29,7 +29,6 @@
 #include "I_Cache.h"
 #include "I_Net.h"
 #include "I_HostDB.h"
-#include "BaseManager.h"
 #include "P_RefCountCache.h"
 
 /*-------------------------------------------------------------------------
@@ -44,100 +43,5 @@
 const char *
 event_int_to_string(int event, int blen, char *buffer)
 {
-  switch (event) {
-  case -1:
-    return "<no event>";
-  case VC_EVENT_READ_READY:
-    return "VC_EVENT_READ_READY";
-  case VC_EVENT_WRITE_READY:
-    return "VC_EVENT_WRITE_READY";
-  case VC_EVENT_READ_COMPLETE:
-    return "VC_EVENT_READ_COMPLETE";
-  case VC_EVENT_WRITE_COMPLETE:
-    return "VC_EVENT_WRITE_COMPLETE";
-  case VC_EVENT_EOS:
-    return "VC_EVENT_EOS";
-  case VC_EVENT_INACTIVITY_TIMEOUT:
-    return "VC_EVENT_INACTIVITY_TIMEOUT";
-  case VC_EVENT_ACTIVE_TIMEOUT:
-    return "VC_EVENT_ACTIVE_TIMEOUT";
-
-  case NET_EVENT_OPEN:
-    return "NET_EVENT_OPEN";
-  case NET_EVENT_OPEN_FAILED:
-    return "NET_EVENT_OPEN_FAILED";
-  case NET_EVENT_ACCEPT:
-    return "NET_EVENT_ACCEPT";
-  case NET_EVENT_ACCEPT_SUCCEED:
-    return "NET_EVENT_ACCEPT_SUCCEED";
-  case NET_EVENT_ACCEPT_FAILED:
-    return "NET_EVENT_ACCEPT_FAILED";
-
-  case EVENT_HOST_DB_LOOKUP:
-    return "EVENT_HOST_DB_LOOKUP";
-  case EVENT_HOST_DB_GET_RESPONSE:
-    return "EVENT_HOST_DB_GET_RESPONSE";
-
-  case DNS_EVENT_EVENTS_START:
-    return "DNS_EVENT_EVENTS_START";
-
-  case REFCOUNT_CACHE_EVENT_SYNC:
-    return "REFCOUNT_CACHE_EVENT_SYNC";
-
-  case CACHE_EVENT_LOOKUP:
-    return "CACHE_EVENT_LOOKUP";
-  case CACHE_EVENT_LOOKUP_FAILED:
-    return "CACHE_EVENT_LOOKUP_FAILED";
-  case CACHE_EVENT_OPEN_READ:
-    return "CACHE_EVENT_OPEN_READ";
-  case CACHE_EVENT_OPEN_READ_FAILED:
-    return "CACHE_EVENT_OPEN_READ_FAILED";
-  case CACHE_EVENT_OPEN_WRITE:
-    return "CACHE_EVENT_OPEN_WRITE";
-  case CACHE_EVENT_OPEN_WRITE_FAILED:
-    return "CACHE_EVENT_OPEN_WRITE_FAILED";
-  case CACHE_EVENT_REMOVE:
-    return "CACHE_EVENT_REMOVE";
-  case CACHE_EVENT_REMOVE_FAILED:
-    return "CACHE_EVENT_REMOVE_FAILED";
-  case CACHE_EVENT_UPDATE:
-    return "CACHE_EVENT_UPDATE";
-  case CACHE_EVENT_UPDATE_FAILED:
-    return "CACHE_EVENT_UPDATE_FAILED";
-  case CACHE_EVENT_LINK:
-    return "CACHE_EVENT_LINK";
-  case CACHE_EVENT_LINK_FAILED:
-    return "CACHE_EVENT_LINK_FAILED";
-  case CACHE_EVENT_DEREF:
-    return "CACHE_EVENT_DEREF";
-  case CACHE_EVENT_DEREF_FAILED:
-    return "CACHE_EVENT_DEREF_FAILED";
-  case CACHE_EVENT_RESPONSE:
-    return "CACHE_EVENT_RESPONSE";
-  case CACHE_EVENT_RESPONSE_MSG:
-    return "CACHE_EVENT_RESPONSE_MSG";
-
-  case MGMT_EVENT_SHUTDOWN:
-    return "MGMT_EVENT_SHUTDOWN";
-  case MGMT_EVENT_RESTART:
-    return "MGMT_EVENT_RESTART";
-  case MGMT_EVENT_BOUNCE:
-    return "MGMT_EVENT_BOUNCE";
-  case MGMT_EVENT_CONFIG_FILE_UPDATE:
-    return "MGMT_EVENT_CONFIG_FILE_UPDATE";
-  case MGMT_EVENT_CLEAR_STATS:
-    return "MGMT_EVENT_CLEAR_STATS";
-  case MGMT_EVENT_HOST_STATUS_UP:
-    return "MGMT_EVENT_HOST_STATUS_UP";
-  case MGMT_EVENT_HOST_STATUS_DOWN:
-    return "MGMT_EVENT_HOST_STATUS_DOWN";
-
-  default:
-    if (buffer != nullptr) {
-      snprintf(buffer, blen, "%d", event);
-      return buffer;
-    } else {
-      return "UNKNOWN_EVENT";
-    }
-  }
+  return "UNKNOWN_EVENT";
 }
diff --git a/src/traffic_server/HostStatus.cc b/src/traffic_server/HostStatus.cc
index 0449aeb..b496cdc 100644
--- a/src/traffic_server/HostStatus.cc
+++ b/src/traffic_server/HostStatus.cc
@@ -20,80 +20,32 @@
   See the License for the specific language governing permissions and
   limitations under the License.
  */
+#include <fstream>
 #include "HostStatus.h"
-#include "ProcessManager.h"
+#include "I_Tasks.h"
 
-inline void
-getStatName(std::string &stat_name, const std::string_view name)
+#include "tscore/BufferWriter.h"
+#include "rpc/jsonrpc/JsonRPC.h"
+#include "shared/rpc/RPCRequests.h"
+
+namespace
 {
-  stat_name.clear();
-  stat_name.append(stat_prefix).append(name);
-}
+const std::string STATUS_LIST_KEY{"statusList"};
+const std::string ERROR_LIST_KEY{"errorList"};
+const std::string HOST_NAME_KEY{"hostname"};
+const std::string STATUS_KEY{"status"};
 
-static void
-mgmt_host_status_up_callback(ts::MemSpan<void> span)
-{
-  MgmtInt op;
-  MgmtMarshallString name;
-  MgmtMarshallInt down_time;
-  MgmtMarshallString reason_str;
-  std::string stat_name;
-  char buf[1024]                         = {0};
-  char *data                             = static_cast<char *>(span.data());
-  auto len                               = span.size();
-  static const MgmtMarshallType fields[] = {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT};
-  Debug("host_statuses", "%s:%s:%d - data: %s, len: %ld\n", __FILE__, __func__, __LINE__, data, len);
+struct HostCmdInfo {
+  TSHostStatus type{TSHostStatus::TS_HOST_STATUS_INIT};
+  unsigned int reasonType{0};
+  std::vector<std::string> hosts;
+  int time{0};
+};
 
-  if (mgmt_message_parse(data, len, fields, countof(fields), &op, &name, &reason_str, &down_time) == -1) {
-    Error("Plugin message - RPC parsing error - message discarded.");
-  }
-  Debug("host_statuses", "op: %ld, name: %s, down_time: %d, reason_str: %s", static_cast<long>(op), name,
-        static_cast<int>(down_time), reason_str);
+} // namespace
 
-  unsigned int reason = Reason::getReason(reason_str);
-
-  getStatName(stat_name, name);
-  if (data != nullptr) {
-    Debug("host_statuses", "marking up server %s", data);
-    HostStatus &hs = HostStatus::instance();
-    if (hs.getHostStat(stat_name, buf, 1024) == REC_ERR_FAIL) {
-      hs.createHostStat(name);
-    }
-    hs.setHostStatus(name, TSHostStatus::TS_HOST_STATUS_UP, down_time, reason);
-  }
-}
-
-static void
-mgmt_host_status_down_callback(ts::MemSpan<void> span)
-{
-  MgmtInt op;
-  MgmtMarshallString name;
-  MgmtMarshallInt down_time;
-  MgmtMarshallString reason_str;
-  std::string stat_name;
-  char *data                             = static_cast<char *>(span.data());
-  char buf[1024]                         = {0};
-  auto len                               = span.size();
-  static const MgmtMarshallType fields[] = {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT};
-  Debug("host_statuses", "%s:%s:%d - data: %s, len: %ld\n", __FILE__, __func__, __LINE__, data, len);
-
-  if (mgmt_message_parse(data, len, fields, countof(fields), &op, &name, &reason_str, &down_time) == -1) {
-    Error("Plugin message - RPC parsing error - message discarded.");
-  }
-  Debug("host_statuses", "op: %ld, name: %s, down_time: %d, reason_str: %s", static_cast<long>(op), name,
-        static_cast<int>(down_time), reason_str);
-
-  unsigned int reason = Reason::getReason(reason_str);
-
-  if (data != nullptr) {
-    Debug("host_statuses", "marking down server %s", name);
-    HostStatus &hs = HostStatus::instance();
-    if (hs.getHostStat(stat_name, buf, 1024) == REC_ERR_FAIL) {
-      hs.createHostStat(name);
-    }
-    hs.setHostStatus(name, TSHostStatus::TS_HOST_STATUS_DOWN, down_time, reason);
-  }
-}
+ts::Rv<YAML::Node> server_get_status(std::string_view const id, YAML::Node const &params);
+ts::Rv<YAML::Node> server_set_status(std::string_view const id, YAML::Node const &params);
 
 HostStatRec::HostStatRec()
   : status(TS_HOST_STATUS_UP),
@@ -182,31 +134,15 @@
   }
 }
 
-static void
-handle_record_read(const RecRecord *rec, void *edata)
-{
-  HostStatus &hs = HostStatus::instance();
-  std::string hostname;
-
-  if (rec) {
-    Debug("host_statuses", "name: %s", rec->name);
-
-    // parse the hostname from the stat name
-    char *s = const_cast<char *>(rec->name);
-    // 1st move the pointer past the stat prefix.
-    s += stat_prefix.length();
-    hostname = s;
-    hs.createHostStat(hostname.c_str(), rec->data.rec_string);
-    HostStatRec h(rec->data.rec_string);
-    hs.loadRecord(hostname, h);
-  }
-}
-
 HostStatus::HostStatus()
 {
   ink_rwlock_init(&host_status_rwlock);
-  pmgmt->registerMgmtCallback(MGMT_EVENT_HOST_STATUS_UP, &mgmt_host_status_up_callback);
-  pmgmt->registerMgmtCallback(MGMT_EVENT_HOST_STATUS_DOWN, &mgmt_host_status_down_callback);
+
+  // register JSON-RPC methods.
+  rpc::add_method_handler("admin_host_set_status", &server_set_status, &rpc::core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+  rpc::add_method_handler("admin_host_get_status", &server_get_status, &rpc::core_ats_rpc_service_provider_handle,
+                          {{rpc::NON_RESTRICTED_API}});
 }
 
 HostStatus::~HostStatus()
@@ -218,14 +154,30 @@
   ink_rwlock_destroy(&host_status_rwlock);
 }
 
+// loads host status persistent store file
 void
-HostStatus::loadHostStatusFromStats()
+HostStatus::loadFromPersistentStore()
 {
-  if (RecLookupMatchingRecords(RECT_PROCESS, stat_prefix.c_str(), handle_record_read, nullptr) != REC_ERR_OKAY) {
-    Error("[HostStatus] - While loading HostStatus stats, there was an Error reading HostStatus stats.");
+  YAML::Node records;
+  std::string fileStore = getHostStatusPersistentFilePath();
+  if (access(fileStore.c_str(), R_OK) == 0) {
+    try {
+      records               = YAML::LoadFile(fileStore.c_str());
+      YAML::Node statusList = records["statuses"];
+      for (YAML::const_iterator it = statusList.begin(); it != statusList.end(); ++it) {
+        const YAML::Node &host = *it;
+        std::string hostName   = host[HOST_NAME_KEY].as<std::string>();
+        std::string status     = host[STATUS_KEY].as<std::string>();
+        HostStatRec h(status);
+        loadRecord(hostName, h);
+      }
+    } catch (std::exception const &ex) {
+      Warning("Error loading and decoding %s : %s", fileStore.c_str(), ex.what());
+    }
   }
 }
 
+// loads in host status record.
 void
 HostStatus::loadRecord(std::string_view name, HostStatRec &h)
 {
@@ -233,32 +185,20 @@
   Debug("host_statuses", "loading host status record for %.*s", int(name.size()), name.data());
   ink_rwlock_wrlock(&host_status_rwlock);
   {
-    if (auto it = hosts_statuses.find(std::string(name)); it != hosts_statuses.end()) {
-      host_stat = it->second;
-    } else {
+    auto it = hosts_statuses.find(std::string(name));
+    if (it == hosts_statuses.end()) {
       host_stat  = static_cast<HostStatRec *>(ats_malloc(sizeof(HostStatRec)));
       *host_stat = h;
       hosts_statuses.emplace(name, host_stat);
     }
   }
   ink_rwlock_unlock(&host_status_rwlock);
-
-  *host_stat = h;
 }
 
 void
 HostStatus::setHostStatus(const std::string_view name, TSHostStatus status, const unsigned int down_time, const unsigned int reason)
 {
   std::string stat_name;
-  char buf[1024] = {0};
-
-  getStatName(stat_name, name);
-
-  if (getHostStat(stat_name, buf, 1024) == REC_ERR_FAIL) {
-    createHostStat(name);
-  }
-
-  RecErrT result = getHostStat(stat_name, buf, 1024);
 
   // update / insert status.
   // using the hash table pointer to store the TSHostStatus value.
@@ -339,21 +279,6 @@
   }
   ink_rwlock_unlock(&host_status_rwlock);
 
-  // update the stats
-  if (result == REC_ERR_OKAY) {
-    std::stringstream status_rec;
-    status_rec << *host_stat;
-    RecSetRecordString(stat_name.c_str(), const_cast<char *>(status_rec.str().c_str()), REC_SOURCE_EXPLICIT, true);
-    if (status == TSHostStatus::TS_HOST_STATUS_UP) {
-      Debug("host_statuses", "set status up for name: %.*s, status: %d, stat_name: %s", int(name.size()), name.data(), status,
-            stat_name.c_str());
-    } else {
-      Debug("host_statuses", "set status down for name: %.*s, status: %d, stat_name: %s", int(name.size()), name.data(), status,
-            stat_name.c_str());
-    }
-  }
-  Debug("host_statuses", "name: %.*s, status: %d", int(name.size()), name.data(), status);
-
   // log it.
   if (status == TSHostStatus::TS_HOST_STATUS_DOWN) {
     Note("Host %.*s has been marked down, down_time: %d - %s.", int(name.size()), name.data(), down_time,
@@ -363,6 +288,29 @@
   }
 }
 
+// retrieve all host statuses.
+void
+HostStatus::getAllHostStatuses(std::vector<HostStatuses> &hosts)
+{
+  if (hosts_statuses.empty()) {
+    return;
+  }
+
+  ink_rwlock_rdlock(&host_status_rwlock);
+  {
+    for (std::pair<std::string, HostStatRec *> hsts : hosts_statuses) {
+      std::stringstream ss;
+      HostStatuses h;
+      h.hostname = hsts.first;
+      ss << *hsts.second;
+      h.status = ss.str();
+      hosts.push_back(h);
+    }
+  }
+  ink_rwlock_unlock(&host_status_rwlock);
+}
+
+// retrieve the named host status.
 HostStatRec *
 HostStatus::getHostStatus(const std::string_view name)
 {
@@ -423,29 +371,163 @@
   return _status;
 }
 
-void
-HostStatus::createHostStat(const std::string_view name, const char *data)
+namespace YAML
 {
-  char buf[1024] = {0};
-  HostStatRec r;
+template <> struct convert<HostCmdInfo> {
+  static bool
+  decode(const Node &node, HostCmdInfo &rhs)
+  {
+    if (auto n = node["operation"]) {
+      auto const &str = n.as<std::string>();
+      if (str == "up") {
+        rhs.type = TSHostStatus::TS_HOST_STATUS_UP;
+      } else if (str == "down") {
+        rhs.type = TSHostStatus::TS_HOST_STATUS_DOWN;
+      } else {
+        // unknown.
+        return false;
+      }
+    } else {
+      return false;
+    }
 
-  std::string stat_name;
-  std::stringstream status_rec;
-  if (data != nullptr) {
-    HostStatRec h(data);
-    r = h;
-  }
-  status_rec << r;
-  getStatName(stat_name, name);
+    if (auto n = node["host"]; n.IsSequence() && n.size()) {
+      for (auto &&it : n) {
+        rhs.hosts.push_back(it.as<std::string>());
+      }
+    } else {
+      return false;
+    }
 
-  if (getHostStat(stat_name, buf, 1024) == REC_ERR_FAIL) {
-    RecRegisterStatString(RECT_PROCESS, stat_name.c_str(), const_cast<char *>(status_rec.str().c_str()), RECP_PERSISTENT);
-    Debug("host_statuses", "stat name: %s, data: %s", stat_name.c_str(), status_rec.str().c_str());
+    if (auto n = node["reason"]) {
+      auto reasonStr = n.as<std::string>();
+      rhs.reasonType = Reason::getReason(reasonStr.c_str());
+    } // manual by default.
+
+    if (auto n = node["time"]) {
+      rhs.time = std::stoi(n.as<std::string>());
+      if (rhs.time < 0) {
+        return false;
+      }
+    } else {
+      return false;
+    }
+
+    return true;
   }
+};
+} // namespace YAML
+
+// JSON-RPC method to retrieve host status information.
+ts::Rv<YAML::Node>
+server_get_status(std::string_view id, YAML::Node const &params)
+{
+  namespace err = rpc::handlers::errors;
+  ts::Rv<YAML::Node> resp;
+  YAML::Node statusList{YAML::NodeType::Sequence}, errorList{YAML::NodeType::Sequence};
+
+  try {
+    if (!params.IsNull() && params.size() > 0) { // returns host statuses for just the ones asked for.
+      for (YAML::const_iterator it = params.begin(); it != params.end(); ++it) {
+        YAML::Node host{YAML::NodeType::Map};
+        auto name             = it->as<std::string>();
+        HostStatRec *host_rec = nullptr;
+        HostStatus &hs        = HostStatus::instance();
+        host_rec              = hs.getHostStatus(name);
+        if (host_rec == nullptr) {
+          Debug("host_statuses", "no record for %s was found", name.c_str());
+          errorList.push_back("no record for " + name + " was found");
+          continue;
+        } else {
+          std::stringstream s;
+          s << *host_rec;
+          host[HOST_NAME_KEY] = name;
+          host[STATUS_KEY]    = s.str();
+          statusList.push_back(host);
+          Debug("host_statuses", "hostname: %s, status: %s", name.c_str(), s.str().c_str());
+        }
+      }
+    } else { // return all host statuses.
+      std::vector<HostStatuses> hostInfo;
+      HostStatus &hs = HostStatus::instance();
+      hs.getAllHostStatuses(hostInfo);
+      for (auto &h : hostInfo) {
+        YAML::Node host{YAML::NodeType::Map};
+        host[HOST_NAME_KEY] = h.hostname;
+        host[STATUS_KEY]    = h.status;
+        statusList.push_back(host);
+      }
+    }
+  } catch (std::exception const &ex) {
+    Debug("host_statuses", "Got an error decoding the parameters: %s", ex.what());
+    errorList.push_back("Error decoding parameters : " + std::string(ex.what()));
+  }
+
+  resp.result()[STATUS_LIST_KEY] = statusList;
+  resp.result()[ERROR_LIST_KEY]  = errorList;
+
+  return resp;
 }
 
-RecErrT
-HostStatus::getHostStat(std::string &stat_name, char *buf, unsigned int buf_len)
+// JSON-RPC method to mark up or down a host.
+ts::Rv<YAML::Node>
+server_set_status(std::string_view id, YAML::Node const &params)
 {
-  return RecGetRecordString(stat_name.c_str(), buf, buf_len, true);
+  Debug("host_statuses", "id=%s", id.data());
+  namespace err = rpc::handlers::errors;
+  ts::Rv<YAML::Node> resp;
+
+  try {
+    if (!params.IsNull()) {
+      auto cmdInfo = params.as<HostCmdInfo>();
+
+      for (auto const &name : cmdInfo.hosts) {
+        HostStatus &hs       = HostStatus::instance();
+        std::string statName = stat_prefix + name;
+        Debug("host_statuses", "marking server %s : %s", name.c_str(),
+              (cmdInfo.type == TSHostStatus::TS_HOST_STATUS_UP ? "up" : "down"));
+        hs.setHostStatus(name.c_str(), cmdInfo.type, cmdInfo.time, cmdInfo.reasonType);
+      }
+    } else {
+      resp.errata().push(err::make_errata(err::Codes::SERVER, "Invalid input parameters, null"));
+    }
+
+    // schedule a write to the persistent store.
+    Debug("host_statuses", "updating persistent store");
+    eventProcessor.schedule_imm(new HostStatusSync, ET_TASK);
+  } catch (std::exception const &ex) {
+    Debug("host_statuses", "Got an error HostCmdInfo decoding: %s", ex.what());
+    resp.errata().push(err::make_errata(err::Codes::SERVER, "Error found during host status set: {}", ex.what()));
+  }
+  return resp;
+}
+
+// method to write host status records to the persistent store.
+void
+HostStatusSync::sync_task()
+{
+  YAML::Node records{YAML::NodeType::Map};
+
+  YAML::Node statusList{YAML::NodeType::Sequence};
+  std::vector<HostStatuses> statuses;
+  HostStatus &hs = HostStatus::instance();
+  hs.getAllHostStatuses(statuses);
+
+  for (auto &&h : statuses) {
+    YAML::Node host{YAML::NodeType::Map};
+    host[HOST_NAME_KEY] = h.hostname;
+    host[STATUS_KEY]    = h.status;
+    statusList.push_back(host);
+  }
+  records["statuses"] = statusList;
+
+  std::ofstream fout;
+  fout.open(hostRecordsFile.c_str(), std::ofstream::out | std::ofstream::trunc);
+  if (fout) {
+    fout << records;
+    fout << '\n';
+    fout.close();
+  } else {
+    Warning("failed to open %s for writing", hostRecordsFile.c_str());
+  }
 }
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index e69b16e..d9f7ee8 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -23,6 +23,8 @@
 
 #include <cstdio>
 #include <atomic>
+#include <tuple>
+#include <unordered_map>
 #include <string_view>
 
 #include "tscore/ink_platform.h"
@@ -75,6 +77,8 @@
 #include "HttpProxyServerMain.h"
 #include "shared/overridable_txn_vars.h"
 
+#include "rpc/jsonrpc/JsonRPC.h"
+
 #include "ts/ts.h"
 
 /****************************************************************
@@ -754,6 +758,20 @@
   return TS_SUCCESS;
 }
 
+static TSReturnCode
+sdk_sanity_check_rpc_handler_options(const TSRPCHandlerOptions *opt)
+{
+  if (nullptr == opt) {
+    return TS_ERROR;
+  }
+
+  if (opt->auth.restricted < 0 || opt->auth.restricted > 1) {
+    return TS_ERROR;
+  }
+
+  return TS_SUCCESS;
+}
+
 /**
   The function checks if the buffer is Modifiable and returns true if
   it is modifiable, else returns false.
@@ -4622,40 +4640,6 @@
 }
 
 TSAction
-TSContSchedule(TSCont contp, TSHRTime timeout)
-{
-  sdk_assert(sdk_sanity_check_iocore_structure(contp) == TS_SUCCESS);
-
-  /* ensure we are on a EThread */
-  sdk_assert(sdk_sanity_check_null_ptr((void *)this_ethread()) == TS_SUCCESS);
-
-  FORCE_PLUGIN_SCOPED_MUTEX(contp);
-
-  INKContInternal *i = reinterpret_cast<INKContInternal *>(contp);
-
-  if (ink_atomic_increment(static_cast<int *>(&i->m_event_count), 1) < 0) {
-    ink_assert(!"not reached");
-  }
-
-  EThread *eth = i->getThreadAffinity();
-  if (eth == nullptr) {
-    eth = this_ethread();
-    i->setThreadAffinity(eth);
-  }
-
-  TSAction action;
-  if (timeout == 0) {
-    action = reinterpret_cast<TSAction>(eth->schedule_imm(i));
-  } else {
-    action = reinterpret_cast<TSAction>(eth->schedule_in(i, HRTIME_MSECONDS(timeout)));
-  }
-
-  /* This is a hack. Should be handled in ink_types */
-  action = (TSAction)((uintptr_t)action | 0x1);
-  return action;
-}
-
-TSAction
 TSContScheduleOnPool(TSCont contp, TSHRTime timeout, TSThreadPool tp)
 {
   sdk_assert(sdk_sanity_check_iocore_structure(contp) == TS_SUCCESS);
@@ -4736,35 +4720,6 @@
 }
 
 TSAction
-TSContScheduleEvery(TSCont contp, TSHRTime every /* millisecs */)
-{
-  sdk_assert(sdk_sanity_check_iocore_structure(contp) == TS_SUCCESS);
-
-  /* ensure we are on a EThread */
-  sdk_assert(sdk_sanity_check_null_ptr((void *)this_ethread()) == TS_SUCCESS);
-
-  FORCE_PLUGIN_SCOPED_MUTEX(contp);
-
-  INKContInternal *i = reinterpret_cast<INKContInternal *>(contp);
-
-  if (ink_atomic_increment(static_cast<int *>(&i->m_event_count), 1) < 0) {
-    ink_assert(!"not reached");
-  }
-
-  EThread *eth = i->getThreadAffinity();
-  if (eth == nullptr) {
-    eth = this_ethread();
-    i->setThreadAffinity(eth);
-  }
-
-  TSAction action = reinterpret_cast<TSAction>(eth->schedule_every(i, HRTIME_MSECONDS(every)));
-
-  /* This is a hack. Should be handled in ink_types */
-  action = (TSAction)((uintptr_t)action | 0x1);
-  return action;
-}
-
-TSAction
 TSContScheduleEveryOnPool(TSCont contp, TSHRTime every, TSThreadPool tp)
 {
   sdk_assert(sdk_sanity_check_iocore_structure(contp) == TS_SUCCESS);
@@ -5721,16 +5676,19 @@
 }
 
 TSReturnCode
-TSHttpTxnAborted(TSHttpTxn txnp)
+TSHttpTxnAborted(TSHttpTxn txnp, bool *client_abort)
 {
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
+  sdk_assert(client_abort != nullptr);
 
-  HttpSM *sm = (HttpSM *)txnp;
+  *client_abort = false;
+  HttpSM *sm    = (HttpSM *)txnp;
   switch (sm->t_state.squid_codes.log_code) {
   case SQUID_LOG_ERR_CLIENT_ABORT:
   case SQUID_LOG_ERR_CLIENT_READ_ERROR:
   case SQUID_LOG_TCP_SWAPFAIL:
     // check for client abort and cache read error
+    *client_abort = true;
     return TS_SUCCESS;
   default:
     break;
@@ -6398,122 +6356,6 @@
   }
 }
 
-// -------------
-/* These are deprecated as of v9.0.0, and will be removed in v10.0.0 */
-TSReturnCode
-TSHttpTxnArgIndexReserve(const char *name, const char *description, int *arg_idx)
-{
-  return TSUserArgIndexReserve(TS_USER_ARGS_TXN, name, description, arg_idx);
-}
-
-TSReturnCode
-TSHttpTxnArgIndexLookup(int arg_idx, const char **name, const char **description)
-{
-  return TSUserArgIndexLookup(TS_USER_ARGS_TXN, arg_idx, name, description);
-}
-
-TSReturnCode
-TSHttpTxnArgIndexNameLookup(const char *name, int *arg_idx, const char **description)
-{
-  return TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, name, arg_idx, description);
-}
-
-TSReturnCode
-TSHttpSsnArgIndexReserve(const char *name, const char *description, int *arg_idx)
-{
-  return TSUserArgIndexReserve(TS_USER_ARGS_SSN, name, description, arg_idx);
-}
-
-TSReturnCode
-TSHttpSsnArgIndexLookup(int arg_idx, const char **name, const char **description)
-{
-  return TSUserArgIndexLookup(TS_USER_ARGS_SSN, arg_idx, name, description);
-}
-
-TSReturnCode
-TSHttpSsnArgIndexNameLookup(const char *name, int *arg_idx, const char **description)
-{
-  return TSUserArgIndexNameLookup(TS_USER_ARGS_SSN, name, arg_idx, description);
-}
-
-TSReturnCode
-TSVConnArgIndexReserve(const char *name, const char *description, int *arg_idx)
-{
-  return TSUserArgIndexReserve(TS_USER_ARGS_VCONN, name, description, arg_idx);
-}
-
-TSReturnCode
-TSVConnArgIndexLookup(int arg_idx, const char **name, const char **description)
-{
-  return TSUserArgIndexLookup(TS_USER_ARGS_VCONN, arg_idx, name, description);
-}
-
-TSReturnCode
-TSVConnArgIndexNameLookup(const char *name, int *arg_idx, const char **description)
-{
-  return TSUserArgIndexNameLookup(TS_USER_ARGS_VCONN, name, arg_idx, description);
-}
-
-void
-TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void *arg)
-{
-  sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
-
-  HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-
-  sm->set_user_arg(arg_idx, arg);
-}
-
-void *
-TSHttpTxnArgGet(TSHttpTxn txnp, int arg_idx)
-{
-  sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
-
-  HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-  return sm->get_user_arg(arg_idx);
-}
-
-void
-TSHttpSsnArgSet(TSHttpSsn ssnp, int arg_idx, void *arg)
-{
-  sdk_assert(sdk_sanity_check_http_ssn(ssnp) == TS_SUCCESS);
-
-  ProxySession *cs = reinterpret_cast<ProxySession *>(ssnp);
-
-  cs->set_user_arg(arg_idx, arg);
-}
-
-void *
-TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx)
-{
-  sdk_assert(sdk_sanity_check_http_ssn(ssnp) == TS_SUCCESS);
-
-  ProxySession *cs = reinterpret_cast<ProxySession *>(ssnp);
-  return cs->get_user_arg(arg_idx);
-}
-
-void
-TSVConnArgSet(TSVConn connp, int arg_idx, void *arg)
-{
-  sdk_assert(sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS);
-  PluginUserArgsMixin *user_args = dynamic_cast<PluginUserArgsMixin *>(reinterpret_cast<VConnection *>(connp));
-  sdk_assert(user_args);
-
-  user_args->set_user_arg(arg_idx, arg);
-}
-
-void *
-TSVConnArgGet(TSVConn connp, int arg_idx)
-{
-  sdk_assert(sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS);
-  PluginUserArgsMixin *user_args = dynamic_cast<PluginUserArgsMixin *>(reinterpret_cast<VConnection *>(connp));
-  sdk_assert(user_args);
-
-  return user_args->get_user_arg(arg_idx);
-}
-
-/* End deprecated Arg functions. */
-
 void
 TSHttpTxnStatusSet(TSHttpTxn txnp, TSHttpStatus status)
 {
@@ -6532,72 +6374,6 @@
   return static_cast<TSHttpStatus>(sm->t_state.http_return_code);
 }
 
-#if TS_VERSION_MAJOR < 10
-/* control channel for HTTP */
-TSReturnCode
-TSHttpTxnCntl(TSHttpTxn txnp, TSHttpCntlTypeExperimental cntl, void *data)
-{
-  sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
-
-  HttpSM *sm = (HttpSM *)txnp;
-
-  switch (cntl) {
-  case TS_HTTP_CNTL_GET_LOGGING_MODE: {
-    if (data == nullptr) {
-      return TS_ERROR;
-    }
-
-    intptr_t *rptr = static_cast<intptr_t *>(data);
-
-    if (sm->t_state.api_info.logging_enabled) {
-      *rptr = (intptr_t)TS_HTTP_CNTL_ON;
-    } else {
-      *rptr = (intptr_t)TS_HTTP_CNTL_OFF;
-    }
-
-    return TS_SUCCESS;
-  }
-
-  case TS_HTTP_CNTL_SET_LOGGING_MODE:
-    if (data != TS_HTTP_CNTL_ON && data != TS_HTTP_CNTL_OFF) {
-      return TS_ERROR;
-    } else {
-      sm->t_state.api_info.logging_enabled = (bool)data;
-      return TS_SUCCESS;
-    }
-    break;
-
-  case TS_HTTP_CNTL_GET_INTERCEPT_RETRY_MODE: {
-    if (data == nullptr) {
-      return TS_ERROR;
-    }
-
-    intptr_t *rptr = static_cast<intptr_t *>(data);
-
-    if (sm->t_state.api_info.retry_intercept_failures) {
-      *rptr = (intptr_t)TS_HTTP_CNTL_ON;
-    } else {
-      *rptr = (intptr_t)TS_HTTP_CNTL_OFF;
-    }
-
-    return TS_SUCCESS;
-  }
-
-  case TS_HTTP_CNTL_SET_INTERCEPT_RETRY_MODE:
-    if (data != TS_HTTP_CNTL_ON && data != TS_HTTP_CNTL_OFF) {
-      return TS_ERROR;
-    } else {
-      sm->t_state.api_info.retry_intercept_failures = (bool)data;
-      return TS_SUCCESS;
-    }
-  default:
-    return TS_ERROR;
-  }
-
-  return TS_ERROR;
-}
-
-#endif
 TSReturnCode
 TSHttpTxnCntlSet(TSHttpTxn txnp, TSHttpCntlType cntl, bool data)
 {
@@ -8143,25 +7919,7 @@
 TSReturnCode
 TSMgmtConfigIntSet(const char *var_name, TSMgmtInt value)
 {
-  TSMgmtInt result;
-  char *buffer;
-
-  // is this a valid integer?
-  if (TSMgmtIntGet(var_name, &result) != TS_SUCCESS) {
-    return TS_ERROR;
-  }
-
-  // construct a buffer
-  int buffer_size = strlen(var_name) + 1 + 32 + 1 + 64 + 1;
-
-  buffer = static_cast<char *>(alloca(buffer_size));
-  snprintf(buffer, buffer_size, "%s %d %" PRId64 "", var_name, MGMT_INT, value);
-
-  // tell manager to set the configuration; note that this is not
-  // transactional (e.g. we return control to the plugin before the
-  // value is committed to disk by the manager)
-  RecSignalManager(MGMT_SIGNAL_PLUGIN_SET_CONFIG, buffer);
-
+  Warning("This API is no longer supported.");
   return TS_SUCCESS;
 }
 
@@ -8850,16 +8608,10 @@
   case TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT:
     ret = _memberp_to_generic(&overridableHttpConfig->connect_attempts_timeout, conv);
     break;
-  case TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT:
-    ret = _memberp_to_generic(&overridableHttpConfig->post_connect_attempts_timeout, conv);
-    break;
   case TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME:
     conv = &HostDBDownServerCacheTimeConv;
     ret  = &overridableHttpConfig->down_server_timeout;
     break;
-  case TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD:
-    ret = _memberp_to_generic(&overridableHttpConfig->client_abort_threshold, conv);
-    break;
   case TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS:
     ret = _memberp_to_generic(&overridableHttpConfig->doc_in_cache_skip_dns, conv);
     break;
@@ -9005,6 +8757,7 @@
   case TS_CONFIG_SSL_CERT_FILEPATH:
   case TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME:
   case TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME:
+  case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS:
     // String, must be handled elsewhere
     break;
   case TS_CONFIG_PARENT_FAILURES_UPDATE_HOSTDB:
@@ -9031,9 +8784,6 @@
   case TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS:
     ret = _memberp_to_generic(&overridableHttpConfig->per_parent_connect_attempts, conv);
     break;
-  case TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT:
-    ret = _memberp_to_generic(&overridableHttpConfig->parent_connect_timeout, conv);
-    break;
   case TS_CONFIG_HTTP_ALLOW_MULTI_RANGE:
     ret = _memberp_to_generic(&overridableHttpConfig->allow_multi_range, conv);
     break;
@@ -9081,12 +8831,6 @@
   // This helps avoiding compiler warnings, yet detect unhandled enum members.
   case TS_CONFIG_NULL:
   case TS_CONFIG_LAST_ENTRY:
-#if TS_VERSION_MAJOR < 10
-
-  // The following is not used in 9.2.x, but preserved for ABI compatibility.
-  case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER:
-
-#endif
     break;
   }
 
@@ -9268,6 +9012,11 @@
       s->t_state.my_txn_conf().ssl_client_ca_cert_filename = const_cast<char *>(value);
     }
     break;
+  case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS:
+    if (value && length > 0) {
+      s->t_state.my_txn_conf().ssl_client_alpn_protocols = const_cast<char *>(value);
+    }
+    break;
   case TS_CONFIG_SSL_CERT_FILEPATH:
     /* noop */
     break;
@@ -10389,6 +10138,15 @@
   return remapUrlGet(txnp, urlLocp, &UrlMappingContainer::getToURL);
 }
 
+tsapi void *
+TSRemapDLHandleGet(TSRemapPluginInfo plugin_info)
+{
+  sdk_assert(sdk_sanity_check_null_ptr(plugin_info));
+  RemapPluginInfo *info = reinterpret_cast<RemapPluginInfo *>(plugin_info);
+
+  return info->dlh();
+}
+
 TSReturnCode
 TSHostnameIsSelf(const char *hostname, size_t hostname_len)
 {
@@ -10476,3 +10234,87 @@
 
   DbgCtl::_rm_reference();
 }
+
+namespace rpc
+{
+extern std::mutex g_rpcHandlingMutex;
+extern std::condition_variable g_rpcHandlingCompletion;
+extern ts::Rv<YAML::Node> g_rpcHandlerResponseData;
+extern bool g_rpcHandlerProcessingCompleted;
+} // namespace rpc
+
+tsapi TSRPCProviderHandle
+TSRPCRegister(const char *provider_name, size_t provider_len, const char *yaml_version, size_t yamlcpp_lib_len)
+{
+  sdk_assert(sdk_sanity_check_null_ptr(yaml_version) == TS_SUCCESS);
+  sdk_assert(sdk_sanity_check_null_ptr(provider_name) == TS_SUCCESS);
+
+  // We want to make sure that plugins are using the same yaml library version as we use internally. Plugins have to cast the TSYaml
+  // to the YAML::Node, in order for them to make sure the version compatibility they need to register here and make sure the
+  // version is the same.
+  if (std::string_view{yaml_version, yamlcpp_lib_len} != YAMLCPP_LIB_VERSION) {
+    return nullptr;
+  }
+
+  rpc::RPCRegistryInfo *info = new rpc::RPCRegistryInfo();
+  info->provider             = {provider_name, provider_len};
+
+  return (TSRPCProviderHandle)info;
+}
+
+tsapi TSReturnCode
+TSRPCRegisterMethodHandler(const char *name, size_t name_len, TSRPCMethodCb callback, TSRPCProviderHandle info,
+                           const TSRPCHandlerOptions *opt)
+{
+  sdk_assert(sdk_sanity_check_rpc_handler_options(opt) == TS_SUCCESS);
+
+  if (!rpc::add_method_handler_from_plugin(
+        {name, name_len},
+        [callback](std::string_view const &id, const YAML::Node &params) -> void {
+          std::string msgId{id.data(), id.size()};
+          callback(msgId.c_str(), (TSYaml)&params);
+        },
+        (const rpc::RPCRegistryInfo *)info, *opt)) {
+    return TS_ERROR;
+  }
+  return TS_SUCCESS;
+}
+
+tsapi TSReturnCode
+TSRPCRegisterNotificationHandler(const char *name, size_t name_len, TSRPCNotificationCb callback, TSRPCProviderHandle info,
+                                 const TSRPCHandlerOptions *opt)
+{
+  sdk_assert(sdk_sanity_check_rpc_handler_options(opt) == TS_SUCCESS);
+
+  if (!rpc::add_notification_handler(
+        {name, name_len}, [callback](const YAML::Node &params) -> void { callback((TSYaml)&params); },
+        (const rpc::RPCRegistryInfo *)info, *opt)) {
+    return TS_ERROR;
+  }
+  return TS_SUCCESS;
+}
+
+tsapi TSReturnCode
+TSRPCHandlerDone(TSYaml resp)
+{
+  Debug("rpc.api", ">> Handler seems to be done");
+  std::lock_guard<std::mutex> lock(rpc::g_rpcHandlingMutex);
+  auto data                            = *(YAML::Node *)resp;
+  rpc::g_rpcHandlerResponseData        = data;
+  rpc::g_rpcHandlerProcessingCompleted = true;
+  rpc::g_rpcHandlingCompletion.notify_one();
+  Debug("rpc.api", ">> all set.");
+  return TS_SUCCESS;
+}
+
+tsapi TSReturnCode
+TSRPCHandlerError(int ec, const char *descr, size_t descr_len)
+{
+  Debug("rpc.api", ">> Handler seems to be done with an error");
+  std::lock_guard<std::mutex> lock(rpc::g_rpcHandlingMutex);
+  rpc::g_rpcHandlerResponseData        = ts::Errata{}.push(1, ec, std::string{descr, descr_len});
+  rpc::g_rpcHandlerProcessingCompleted = true;
+  rpc::g_rpcHandlingCompletion.notify_one();
+  Debug("rpc.api", ">> error  flagged.");
+  return TS_SUCCESS;
+}
diff --git a/src/traffic_server/InkAPITest.cc b/src/traffic_server/InkAPITest.cc
index 84eea10..74a26b4 100644
--- a/src/traffic_server/InkAPITest.cc
+++ b/src/traffic_server/InkAPITest.cc
@@ -6636,8 +6636,10 @@
   ORIG_TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK,
   ORIG_TS_HTTP_PRE_REMAP_HOOK,
   ORIG_TS_HTTP_POST_REMAP_HOOK,
+  ORIG_TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK,
   ORIG_TS_HTTP_RESPONSE_CLIENT_HOOK,
-  ORIG_TS_SSL_FIRST_HOOK,
+  ORIG_TS_HTTP_REQUEST_CLIENT_HOOK,
+  ORIG_TS_SSL_FIRST_HOOK   = 201,
   ORIG_TS_VCONN_START_HOOK = ORIG_TS_SSL_FIRST_HOOK,
   ORIG_TS_VCONN_CLOSE_HOOK,
   ORIG_TS_SSL_CLIENT_HELLO_HOOK,
@@ -6649,7 +6651,6 @@
   ORIG_TS_VCONN_OUTBOUND_START_HOOK,
   ORIG_TS_VCONN_OUTBOUND_CLOSE_HOOK,
   ORIG_TS_SSL_LAST_HOOK = ORIG_TS_VCONN_OUTBOUND_CLOSE_HOOK,
-  ORIG_TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK,
   ORIG_TS_HTTP_LAST_HOOK
 };
 
@@ -8629,9 +8630,7 @@
    "proxy.config.http.connect_attempts_max_retries_dead_server",
    "proxy.config.http.connect_attempts_rr_retries",
    "proxy.config.http.connect_attempts_timeout",
-   "proxy.config.http.post_connect_attempts_timeout",
    "proxy.config.http.down_server.cache_time",
-   "proxy.config.http.down_server.abort_threshold",
    "proxy.config.http.doc_in_cache_skip_dns",
    "proxy.config.http.background_fill_active_timeout",
    "proxy.config.http.response_server_str",
@@ -8685,7 +8684,6 @@
    "proxy.config.http.parent_proxy.fail_threshold",
    "proxy.config.http.parent_proxy.retry_time",
    "proxy.config.http.parent_proxy.per_parent_connect_attempts",
-   "proxy.config.http.parent_proxy.connect_attempts_timeout",
    "proxy.config.http.normalize_ae",
    "proxy.config.http.insert_forwarded",
    "proxy.config.http.proxy_protocol_out",
@@ -8695,14 +8693,12 @@
    OutboundConnTrack::CONFIG_VAR_MIN,
    OutboundConnTrack::CONFIG_VAR_MAX,
    OutboundConnTrack::CONFIG_VAR_MATCH,
-#if TS_VERSION_MAJOR < 10
-   "proxy.config.ssl.client.verify.server",
-#endif
    "proxy.config.ssl.client.verify.server.policy",
    "proxy.config.ssl.client.verify.server.properties",
    "proxy.config.ssl.client.sni_policy",
    "proxy.config.ssl.client.private_key.filename",
    "proxy.config.ssl.client.CA.cert.filename",
+   "proxy.config.ssl.client.alpn_protocols",
    "proxy.config.hostdb.ip_resolve",
    "proxy.config.http.connect.dead.policy",
    "proxy.config.http.max_proxy_cycles",
@@ -8735,14 +8731,6 @@
   *pstatus = REGRESSION_TEST_INPROGRESS;
   for (int i = 0; i < static_cast<int>(SDK_Overridable_Configs.size()); ++i) {
     std::string_view conf{SDK_Overridable_Configs[i]};
-#if TS_VERSION_MAJOR < 10
-    if (conf == "proxy.config.ssl.client.verify.server") {
-      // TODO: remove this in 10.x. Kept here because we keep the
-      // `TS_CONFIG_SSL_CLIENT_VERIFY_SERVER` in 9.x to preserve ABI
-      // compatibility in the 9.x releases.
-      continue;
-    }
-#endif
 
     if (TS_SUCCESS == TSHttpTxnConfigFind(conf.data(), -1, &key, &type)) {
       if (key != i) {
diff --git a/src/traffic_server/Makefile.inc b/src/traffic_server/Makefile.inc
index c816e35..5b90af7 100644
--- a/src/traffic_server/Makefile.inc
+++ b/src/traffic_server/Makefile.inc
@@ -34,12 +34,15 @@
 	-I$(abs_top_srcdir)/proxy/shared \
 	-I$(abs_top_srcdir)/mgmt \
 	-I$(abs_top_srcdir)/mgmt/utils \
+        @SWOC_INCLUDES@ \
 	$(TS_INCLUDES) \
 	@OPENSSL_INCLUDES@ \
-	@BORINGOCSP_INCLUDES@
+	@BORINGOCSP_INCLUDES@ \
+	@YAMLCPP_INCLUDES@
 
 traffic_server_traffic_server_LDFLAGS = \
 	$(AM_LDFLAGS) \
+        @SWOC_LDFLAGS@ \
 	@YAMLCPP_LDFLAGS@ \
 	@BORINGOCSP_LDFLAGS@
 
@@ -53,6 +56,8 @@
 	traffic_server/InkAPI.cc \
 	traffic_server/InkIOCoreAPI.cc \
 	traffic_server/SocksProxy.cc \
+	traffic_server/RpcAdminPubHandlers.cc \
+	traffic_server/RpcAdminPubHandlers.h \
 	shared/overridable_txn_vars.cc \
 	traffic_server/traffic_server.cc
 
@@ -82,12 +87,17 @@
 	$(top_builddir)/iocore/net/libinknet.a \
 	$(top_builddir)/src/records/librecords_p.a \
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/mgmt/rpc/libjsonrpc_server.la \
+	$(top_builddir)/mgmt/rpc/libjsonrpc_protocol.la \
+	$(top_builddir)/mgmt/config/libconfigmanager.la \
+	$(top_builddir)/mgmt/rpc/librpcpublichandlers.la \
 	@HWLOC_LIBS@ \
 	@LIBPCRE@ \
 	@LIBRESOLV@ \
 	@LIBZ@ \
 	@LIBLZMA@ \
 	@LIBPROFILER@ \
+        @SWOC_LIBS@ \
 	@OPENSSL_LIBS@ \
 	@YAMLCPP_LIBS@ \
 	@BORINGOCSP_LIBS@ \
@@ -103,4 +113,8 @@
   $(top_builddir)/proxy/http3/libhttp3.a \
   $(top_builddir)/iocore/net/quic/libquic.a \
   $(top_builddir)/iocore/eventsystem/libinkevent.a
+if USE_QUICHE
+traffic_server_traffic_server_LDADD += \
+  $(QUICHE_LIB)
+endif
 endif
diff --git a/src/traffic_server/RpcAdminPubHandlers.cc b/src/traffic_server/RpcAdminPubHandlers.cc
new file mode 100644
index 0000000..8ca8b9d
--- /dev/null
+++ b/src/traffic_server/RpcAdminPubHandlers.cc
@@ -0,0 +1,74 @@
+/**
+   @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 "rpc/jsonrpc/JsonRPC.h"
+
+// Admin API Implementation headers.
+#include "rpc/handlers/config/Configuration.h"
+#include "rpc/handlers/records/Records.h"
+#include "rpc/handlers/storage/Storage.h"
+#include "rpc/handlers/server/Server.h"
+#include "rpc/handlers/plugins/Plugins.h"
+
+#include "RpcAdminPubHandlers.h"
+namespace rpc::admin
+{
+void
+register_admin_jsonrpc_handlers()
+{
+  // Config
+  using namespace rpc::handlers::config;
+  rpc::add_method_handler("admin_config_set_records", &set_config_records, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+  rpc::add_method_handler("admin_config_reload", &reload_config, &core_ats_rpc_service_provider_handle, {{rpc::RESTRICTED_API}});
+
+  // Records
+  using namespace rpc::handlers::records;
+  rpc::add_method_handler("admin_lookup_records", &lookup_records, &core_ats_rpc_service_provider_handle,
+                          {{rpc::NON_RESTRICTED_API}});
+  rpc::add_method_handler("admin_clear_all_metrics_records", &clear_all_metrics_records, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+  rpc::add_method_handler("admin_clear_metrics_records", &clear_metrics_records, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+
+  // plugin
+  using namespace rpc::handlers::plugins;
+  rpc::add_method_handler("admin_plugin_send_basic_msg", &plugin_send_basic_msg, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+
+  // server
+  using namespace rpc::handlers::server;
+  rpc::add_method_handler("admin_server_start_drain", &server_start_drain, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+  rpc::add_method_handler("admin_server_stop_drain", &server_stop_drain, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+  rpc::add_notification_handler("admin_server_shutdown", &server_shutdown, &core_ats_rpc_service_provider_handle,
+                                {{rpc::RESTRICTED_API}});
+  rpc::add_notification_handler("admin_server_restart", &server_shutdown, &core_ats_rpc_service_provider_handle,
+                                {{rpc::RESTRICTED_API}});
+
+  // storage
+  using namespace rpc::handlers::storage;
+  rpc::add_method_handler("admin_storage_set_device_offline", &set_storage_offline, &core_ats_rpc_service_provider_handle,
+                          {{rpc::RESTRICTED_API}});
+  rpc::add_method_handler("admin_storage_get_device_status", &get_storage_status, &core_ats_rpc_service_provider_handle,
+                          {{rpc::NON_RESTRICTED_API}});
+}
+} // namespace rpc::admin
diff --git a/plugins/experimental/mysql_remap/default.h b/src/traffic_server/RpcAdminPubHandlers.h
similarity index 78%
copy from plugins/experimental/mysql_remap/default.h
copy to src/traffic_server/RpcAdminPubHandlers.h
index 7cd5a84..ff29abc 100644
--- a/plugins/experimental/mysql_remap/default.h
+++ b/src/traffic_server/RpcAdminPubHandlers.h
@@ -1,4 +1,6 @@
-/*
+/**
+   @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
@@ -18,5 +20,8 @@
 
 #pragma once
 
-static const char *PLUGIN_NAME = "mysql_remap";
-#define QSIZE 2048
+namespace rpc::admin
+{
+/// Initialize and register all public handler that will be exposed to the JSON RPC server.
+void register_admin_jsonrpc_handlers();
+} // namespace rpc::admin
\ No newline at end of file
diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc
index 7c067e1..ec544ac 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -79,7 +79,6 @@
 #include "RecordsConfig.h"
 #include "records/I_RecProcess.h"
 #include "Transform.h"
-#include "ProcessManager.h"
 #include "ProxyConfig.h"
 #include "HttpProxyServerMain.h"
 #include "HttpBodyFactory.h"
@@ -104,6 +103,15 @@
 #include "tscore/ink_config.h"
 #include "P_SSLClientUtils.h"
 
+// Mgmt Admin public handlers
+#include "RpcAdminPubHandlers.h"
+
+// Json Rpc stuffs
+#include "rpc/jsonrpc/JsonRPCManager.h"
+#include "rpc/server/RPCServer.h"
+
+#include "config/FileManager.h"
+
 #if TS_USE_QUIC == 1
 #include "Http3.h"
 #include "Http3Config.h"
@@ -121,16 +129,11 @@
 //
 #define DEFAULT_COMMAND_FLAG 0
 
-#define DEFAULT_REMOTE_MANAGEMENT_FLAG 0
 #define DEFAULT_DIAGS_LOG_FILENAME "diags.log"
 static char diags_log_filename[PATH_NAME_MAX] = DEFAULT_DIAGS_LOG_FILENAME;
 
 static const long MAX_LOGIN = ink_login_name_max();
 
-static void mgmt_restart_shutdown_callback(ts::MemSpan<void>);
-static void mgmt_drain_callback(ts::MemSpan<void>);
-static void mgmt_storage_device_cmd_callback(int cmd, std::string_view const &arg);
-static void mgmt_lifecycle_msg_callback(ts::MemSpan<void>);
 static void init_ssl_ctx_callback(void *ctx, bool server);
 static void load_ssl_file_callback(const char *ssl_file);
 static void task_threads_started_callback();
@@ -160,7 +163,6 @@
 
 static char command_string[512] = "";
 static char conf_dir[512]       = "";
-int remote_management_flag      = DEFAULT_REMOTE_MANAGEMENT_FLAG;
 static char bind_stdout[512]    = "";
 static char bind_stderr[512]    = "";
 
@@ -220,7 +222,6 @@
 #endif
 
   {"interval", 'i', "Statistics Interval", "I", &show_statistics, "PROXY_STATS_INTERVAL", nullptr},
-  {"remote_management", 'M', "Remote Management", "T", &remote_management_flag, "PROXY_REMOTE_MANAGEMENT", nullptr},
   {"command", 'C',
    "Maintenance Command to Execute\n"
    "      Commands: list, check, clear, clear_cache, clear_hostdb, verify_config, verify_global_plugin, verify_remap_plugin, help",
@@ -251,7 +252,11 @@
       hook = hook->next();
     }
 
-    pmgmt->stop();
+    // if the jsonrpc feature was disabled, the object will not be created.
+    if (jsonrpcServer != nullptr) {
+      jsonrpcServer->stop_thread();
+    }
+
     TSSystemState::shut_down_event_system();
     delete this;
     return EVENT_CONT;
@@ -306,12 +311,10 @@
       if (RecGetRecordInt("proxy.config.stop.shutdown_timeout", &timeout) == REC_ERR_OKAY && timeout) {
         RecSetRecordInt("proxy.node.config.draining", 1, REC_SOURCE_DEFAULT);
         TSSystemState::drain(true);
-        if (!remote_management_flag) {
-          // Close listening sockets here only if TS is running standalone
-          RecInt close_sockets = 0;
-          if (RecGetRecordInt("proxy.config.restart.stop_listening", &close_sockets) == REC_ERR_OKAY && close_sockets) {
-            stop_HttpProxyServer();
-          }
+        // Close listening sockets here only if TS is running standalone
+        RecInt close_sockets = 0;
+        if (RecGetRecordInt("proxy.config.restart.stop_listening", &close_sockets) == REC_ERR_OKAY && close_sockets) {
+          stop_HttpProxyServer();
         }
       }
 
@@ -378,29 +381,45 @@
 class DiagsLogContinuation : public Continuation
 {
 public:
-  DiagsLogContinuation() : Continuation(new_ProxyMutex()) { SET_HANDLER(&DiagsLogContinuation::periodic); }
+  DiagsLogContinuation() : Continuation(new_ProxyMutex())
+  {
+    SET_HANDLER(&DiagsLogContinuation::periodic);
+
+    char *configured_traffic_out_name(REC_ConfigReadString("proxy.config.output.logfile"));
+    traffic_out_name = std::string(configured_traffic_out_name);
+    ats_free(configured_traffic_out_name);
+  }
+
   int
   periodic(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
   {
     Debug("log", "in DiagsLogContinuation, checking on diags.log");
 
-    // First, let us update the rolling config values for diagslog. We
-    // do not need to update the config values for outputlog because
-    // traffic_server never actually rotates outputlog. outputlog is always
-    // rotated in traffic_manager. The reason being is that it is difficult
-    // to send a notification from TS to TM, informing TM that outputlog has
-    // been rolled. It is much easier sending a notification (in the form
-    // of SIGUSR2) from TM -> TS.
-    int diags_log_roll_int    = (int)REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_interval_sec");
-    int diags_log_roll_size   = (int)REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_size_mb");
-    int diags_log_roll_enable = (int)REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_enabled");
-    diags()->config_roll_diagslog((RollingEnabledValues)diags_log_roll_enable, diags_log_roll_int, diags_log_roll_size);
+    // First, let us update the rolling config values for diagslog.
+    int diags_log_roll_int    = static_cast<int>(REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_interval_sec"));
+    int diags_log_roll_size   = static_cast<int>(REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_size_mb"));
+    int diags_log_roll_enable = static_cast<int>(REC_ConfigReadInteger("proxy.config.diags.logfile.rolling_enabled"));
+    diags()->config_roll_diagslog(static_cast<RollingEnabledValues>(diags_log_roll_enable), diags_log_roll_int,
+                                  diags_log_roll_size);
 
     if (diags()->should_roll_diagslog()) {
       Note("Rolled %s", diags_log_filename);
     }
+
+    int output_log_roll_int    = static_cast<int>(REC_ConfigReadInteger("proxy.config.output.logfile.rolling_interval_sec"));
+    int output_log_roll_size   = static_cast<int>(REC_ConfigReadInteger("proxy.config.output.logfile.rolling_size_mb"));
+    int output_log_roll_enable = static_cast<int>(REC_ConfigReadInteger("proxy.config.output.logfile.rolling_enabled"));
+    diags()->config_roll_outputlog(static_cast<RollingEnabledValues>(output_log_roll_enable), output_log_roll_int,
+                                   output_log_roll_size);
+
+    if (diags()->should_roll_outputlog()) {
+      Note("Rolled %s", traffic_out_name.c_str());
+    }
     return EVENT_CONT;
   }
+
+private:
+  std::string traffic_out_name;
 };
 
 class MemoryLimit : public Continuation
@@ -651,28 +670,10 @@
 {
   mgmt_use_syslog();
 
-  // Temporary Hack to Enable Communication with LocalManager
-  if (getenv("PROXY_REMOTE_MGMT")) {
-    remote_management_flag = true;
-  }
-
-  if (remote_management_flag) {
-    // We are being managed by traffic_manager, TERM ourselves if it goes away.
-    EnableDeathSignal(SIGTERM);
-  }
-
-  RecProcessInit(remote_management_flag ? RECM_CLIENT : RECM_STAND_ALONE, diags());
+  RecProcessInit(RECM_STAND_ALONE, diags());
   LibRecordsConfigInit();
 
-  // Start up manager
-  pmgmt = new ProcessManager(remote_management_flag);
-
-  // Lifecycle callbacks can potentially be invoked from this thread, so force thread initialization
-  // to make the TS API work.
-  pmgmt->start(TSThreadInit, TSThreadDestroy);
-
-  RecProcessInitMessage(remote_management_flag ? RECM_CLIENT : RECM_STAND_ALONE);
-  pmgmt->reconfigure();
+  RecProcessInitMessage(RECM_STAND_ALONE);
   check_config_directories();
 
   //
@@ -689,6 +690,45 @@
                         RECP_NON_PERSISTENT);
 }
 
+extern void initializeRegistry();
+
+static void
+initialize_file_manager()
+{
+  initializeRegistry();
+}
+
+std::tuple<bool, std::string>
+initialize_jsonrpc_server()
+{
+  std::tuple<bool, std::string> ok{true, {}};
+  auto filePath = RecConfigReadConfigPath("proxy.config.jsonrpc.filename", ts::filename::JSONRPC);
+
+  auto serverConfig = rpc::config::RPCConfig{};
+  serverConfig.load_from_file(filePath);
+  if (!serverConfig.is_enabled()) {
+    Debug("rpc.init", "JSONRPC Disabled");
+    return ok;
+  }
+
+  // create and start the server.
+  try {
+    jsonrpcServer = new rpc::RPCServer{serverConfig};
+    jsonrpcServer->start_thread(TSThreadInit, TSThreadDestroy);
+  } catch (std::exception const &ex) {
+    // Only the constructor throws, so if we are here there should be no
+    // jsonrpcServer object.
+    ink_assert(jsonrpcServer == nullptr);
+    std::string msg;
+    return {false, ts::bwprint(msg, "Server failed: '{}'", ex.what())};
+  }
+  // Register admin handlers.
+  rpc::admin::register_admin_jsonrpc_handlers();
+  Debug("rpc.init", "JSONRPC. Public admin handlers registered.");
+
+  return ok;
+}
+
 #define CMD_ERROR -2      // serious error, exit maintenance mode
 #define CMD_FAILED -1     // error, but recoverable
 #define CMD_OK 0          // ok, or minor (user) error
@@ -894,7 +934,7 @@
   // initialize logging since a plugin
   // might call TS_ERROR which needs
   // log_rsb to be init'ed
-  Log::init(DEFAULT_REMOTE_MANAGEMENT_FLAG);
+  Log::init();
 
   if (*conf_dir) {
     fprintf(stderr, "NOTE: VERIFY config dir: %s...\n\n", conf_dir);
@@ -1209,7 +1249,7 @@
              "%d (throttle) + %d (internal use) > %d (file descriptor limit), "
              "using throttle of %d",
              fds_throttle, THROTTLE_FD_HEADROOM, fds_limit, new_fds_throttle);
-    SignalWarning(MGMT_SIGNAL_SYSTEM_ERROR, msg);
+    Warning("%s", msg);
   }
 }
 
@@ -1372,16 +1412,10 @@
     NET_READ_DYN_SUM(net_calls_to_readfromnet_stat, sval);
     int64_t d_rb = sval - last_rb;
     last_rb += d_rb;
-    NET_READ_DYN_SUM(net_calls_to_readfromnet_afterpoll_stat, sval);
-    int64_t d_r = sval - last_r;
-    last_r += d_r;
 
     NET_READ_DYN_SUM(net_calls_to_writetonet_stat, sval);
     int64_t d_wb = sval - last_wb;
     last_wb += d_wb;
-    NET_READ_DYN_SUM(net_calls_to_writetonet_afterpoll_stat, sval);
-    int64_t d_w = sval - last_w;
-    last_w += d_w;
 
     NET_READ_DYN_STAT(net_read_bytes_stat, sval, cval);
     int64_t d_nrb = sval - last_nrb;
@@ -1401,9 +1435,8 @@
     NET_READ_DYN_STAT(net_handler_run_stat, sval, cval);
     int64_t d_p = cval - last_p;
     last_p += d_p;
-    printf("%" PRId64 ":%" PRId64 " %" PRId64 ":%" PRId64 " %" PRId64 ":%" PRId64 " %" PRId64 ":%" PRId64 " %" PRId64 " %" PRId64
-           "\n",
-           d_rb, d_r, d_wb, d_w, d_nrb, d_nr, d_nwb, d_nw, d_o, d_p);
+    printf("%" PRId64 ":%" PRId64 ":%" PRId64 ":%" PRId64 " %" PRId64 ":%" PRId64 " %" PRId64 " %" PRId64 "\n", d_rb, d_wb, d_nrb,
+           d_nr, d_nwb, d_nw, d_o, d_p);
 #ifdef ENABLE_TIME_TRACE
     int i;
     fprintf(fp, "immediate_events_time_dist\n");
@@ -1782,6 +1815,9 @@
   // Local process manager
   initialize_process_manager();
 
+  // Initialize file manager for TS.
+  initialize_file_manager();
+
   // Set the core limit for the process
   init_core_size();
   init_system();
@@ -1792,14 +1828,14 @@
   // Restart syslog now that we have configuration info
   syslog_log_configure();
 
-  // Register stats if standalone
-  if (DEFAULT_REMOTE_MANAGEMENT_FLAG == remote_management_flag) {
-    RecRegisterStatInt(RECT_NODE, "proxy.node.config.reconfigure_time", time(nullptr), RECP_NON_PERSISTENT);
-    RecRegisterStatInt(RECT_NODE, "proxy.node.config.reconfigure_required", 0, RECP_NON_PERSISTENT);
-    RecRegisterStatInt(RECT_NODE, "proxy.node.config.restart_required.proxy", 0, RECP_NON_PERSISTENT);
-    RecRegisterStatInt(RECT_NODE, "proxy.node.config.restart_required.manager", 0, RECP_NON_PERSISTENT);
-    RecRegisterStatInt(RECT_NODE, "proxy.node.config.draining", 0, RECP_NON_PERSISTENT);
-  }
+  // Register stats
+  RecRegisterStatInt(RECT_NODE, "proxy.node.config.reconfigure_time", time(nullptr), RECP_NON_PERSISTENT);
+  RecRegisterStatInt(RECT_NODE, "proxy.node.config.reconfigure_required", 0, RECP_NON_PERSISTENT);
+  RecRegisterStatInt(RECT_NODE, "proxy.node.config.restart_required.proxy", 0, RECP_NON_PERSISTENT);
+  RecRegisterStatInt(RECT_NODE, "proxy.node.config.restart_required.manager", 0, RECP_NON_PERSISTENT);
+  RecRegisterStatInt(RECT_NODE, "proxy.node.config.draining", 0, RECP_NON_PERSISTENT);
+  RecRegisterStatInt(RECT_NODE, "proxy.node.proxy_running", 1, RECP_NON_PERSISTENT);
+  RecSetRecordInt("proxy.node.restarts.proxy.start_time", time(nullptr), REC_SOURCE_DEFAULT);
 
   // init huge pages
   int enabled;
@@ -1894,6 +1930,11 @@
   }
 #endif
 
+  // JSONRPC server and handlers
+  if (auto &&[ok, msg] = initialize_jsonrpc_server(); !ok) {
+    Warning("JSONRPC server could not be started.\n  Why?: '%s' ... Continuing without it.", msg.c_str());
+  }
+
   // setup callback for tracking remap included files
   load_remap_file_cb = load_config_file_callback;
 
@@ -1903,7 +1944,7 @@
   // has other dependencies. Hopefully not in prep_HttpProxyServer().
   HttpConfig::startup();
 #if TS_USE_QUIC == 1
-  Http3Config::startup();
+  ts::Http3Config::startup();
 #endif
 
   /* Set up the machine with the outbound address if that's set,
@@ -1932,9 +1973,6 @@
   RecRegisterStatString(RECT_PROCESS, "proxy.process.version.server.uuid", (char *)Machine::instance()->uuid.getString(),
                         RECP_NON_PERSISTENT);
 
-  // pmgmt->start() must occur after initialization of Diags but
-  // before calling RecProcessInit()
-
   REC_ReadConfigInteger(res_track_memory, "proxy.config.res_track_memory");
 
   init_http_header();
@@ -2072,7 +2110,7 @@
     RecProcessStart();
     initCacheControl();
     IpAllow::startup();
-    HostStatus::instance().loadHostStatusFromStats();
+    HostStatus::instance().loadFromPersistentStore();
     netProcessor.init_socks();
     ParentConfig::startup();
     SplitDNSConfig::startup();
@@ -2091,10 +2129,10 @@
 
     dnsProcessor.start(0, stacksize);
     if (hostDBProcessor.start() < 0)
-      SignalWarning(MGMT_SIGNAL_SYSTEM_ERROR, "bad hostdb or storage configuration, hostdb disabled");
+      Warning("bad hostdb or storage configuration, hostdb disabled");
 
     // initialize logging (after event and net processor)
-    Log::init(remote_management_flag ? 0 : Log::NO_REMOTE_MANAGEMENT);
+    Log::init(Log::NO_REMOTE_MANAGEMENT);
 
     (void)parsePluginConfig();
 
@@ -2114,8 +2152,7 @@
 #if TS_USE_QUIC == 1
     quic_NetProcessor.start(-1, stacksize);
 #endif
-    pmgmt->registerPluginCallbacks(global_config_cbs);
-
+    FileManager::instance().registerConfigPluginCallbacks(global_config_cbs);
     cacheProcessor.afterInitCallbackSet(&CB_After_Cache_Init);
     cacheProcessor.start();
 
@@ -2123,6 +2160,8 @@
     if (!num_of_udp_threads) {
       REC_ReadConfigInteger(num_of_udp_threads, "proxy.config.udp.threads");
     }
+
+    udpNet.register_event_type();
     if (num_of_udp_threads) {
       udpNet.start(num_of_udp_threads, stacksize);
       eventProcessor.thread_group[ET_UDP]._afterStartCallback = init_HttpProxyServer;
@@ -2195,18 +2234,6 @@
       start_SocksProxy(netProcessor.socks_conf_stuff->accept_port);
     }
 
-    pmgmt->registerMgmtCallback(MGMT_EVENT_SHUTDOWN, &mgmt_restart_shutdown_callback);
-    pmgmt->registerMgmtCallback(MGMT_EVENT_RESTART, &mgmt_restart_shutdown_callback);
-    pmgmt->registerMgmtCallback(MGMT_EVENT_DRAIN, &mgmt_drain_callback);
-
-    // Callback for various storage commands. These all go to the same function so we
-    // pass the event code along so it can do the right thing. We cast that to <int> first
-    // just to be safe because the value is a #define, not a typed value.
-    pmgmt->registerMgmtCallback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, [](ts::MemSpan<void> span) -> void {
-      mgmt_storage_device_cmd_callback(MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE, span.view());
-    });
-    pmgmt->registerMgmtCallback(MGMT_EVENT_LIFECYCLE_MESSAGE, &mgmt_lifecycle_msg_callback);
-
     ink_set_thread_name("[TS_MAIN]");
 
     Note("traffic server running");
@@ -2241,59 +2268,6 @@
   delete main_thread;
 }
 
-static void mgmt_restart_shutdown_callback(ts::MemSpan<void>)
-{
-  sync_cache_dir_on_shutdown();
-}
-
-static void
-mgmt_drain_callback(ts::MemSpan<void> span)
-{
-  char *arg = span.rebind<char>().data();
-  TSSystemState::drain(span.size() == 2 && arg[0] == '1');
-  RecSetRecordInt("proxy.node.config.draining", TSSystemState::is_draining() ? 1 : 0, REC_SOURCE_DEFAULT);
-}
-
-static void
-mgmt_storage_device_cmd_callback(int cmd, std::string_view const &arg)
-{
-  // data is the device name to control
-  CacheDisk *d = cacheProcessor.find_by_path(arg.data(), int(arg.size()));
-
-  if (d) {
-    switch (cmd) {
-    case MGMT_EVENT_STORAGE_DEVICE_CMD_OFFLINE:
-      Debug("server", "Marking %.*s offline", int(arg.size()), arg.data());
-      cacheProcessor.mark_storage_offline(d, /* admin */ true);
-      break;
-    }
-  }
-}
-
-static void
-mgmt_lifecycle_msg_callback(ts::MemSpan<void> span)
-{
-  APIHook *hook = lifecycle_hooks->get(TS_LIFECYCLE_MSG_HOOK);
-  TSPluginMsg msg;
-  MgmtInt op;
-  MgmtMarshallString tag;
-  MgmtMarshallData payload;
-  static const MgmtMarshallType fields[] = {MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_DATA};
-
-  if (mgmt_message_parse(span.data(), span.size(), fields, countof(fields), &op, &tag, &payload) == -1) {
-    Error("Plugin message - RPC parsing error - message discarded.");
-  } else {
-    msg.tag       = tag;
-    msg.data      = payload.ptr;
-    msg.data_size = payload.len;
-    while (hook) {
-      TSPluginMsg tmp(msg); // Just to make sure plugins don't mess this up for others.
-      hook->invoke(TS_EVENT_LIFECYCLE_MSG, &tmp);
-      hook = hook->next();
-    }
-  }
-}
-
 static void
 init_ssl_ctx_callback(void *ctx, bool server)
 {
@@ -2310,13 +2284,13 @@
 static void
 load_ssl_file_callback(const char *ssl_file)
 {
-  pmgmt->signalConfigFileChild(ts::filename::SSL_MULTICERT, ssl_file);
+  FileManager::instance().configFileChild(ts::filename::SSL_MULTICERT, ssl_file);
 }
 
 void
 load_config_file_callback(const char *parent_file, const char *remap_file)
 {
-  pmgmt->signalConfigFileChild(parent_file, remap_file);
+  FileManager::instance().configFileChild(parent_file, remap_file);
 }
 
 static void
diff --git a/src/traffic_top/Makefile.inc b/src/traffic_top/Makefile.inc
index d7efc02..b9eca9b 100644
--- a/src/traffic_top/Makefile.inc
+++ b/src/traffic_top/Makefile.inc
@@ -21,34 +21,31 @@
 bin_PROGRAMS += traffic_top/traffic_top
 
 traffic_top_traffic_top_CPPFLAGS = \
-    $(AM_CPPFLAGS) \
-	$(iocore_include_dirs) \
+	$(AM_CPPFLAGS) \
 	-I$(abs_top_srcdir)/include \
-	-I$(abs_top_srcdir)/lib \
-	-I$(abs_top_srcdir)/mgmt \
-	-I$(abs_top_srcdir)/mgmt/api/include \
 	$(TS_INCLUDES) \
+        @SWOC_INCLUDES@ \
 	@CURL_CFLAGS@ \
-	@CURSES_CFLAGS@
+	@CURSES_CFLAGS@ \
+	@YAMLCPP_INCLUDES@
 
 traffic_top_traffic_top_LDFLAGS = \
-    $(AM_LDFLAGS) \
+	$(AM_LDFLAGS) \
 	@CURSES_LDFLAGS@ \
-	@OPENSSL_LDFLAGS@
+	@OPENSSL_LDFLAGS@ \
+	@SWOC_LIBS@ @YAMLCPP_LIBS@
+
 
 traffic_top_traffic_top_SOURCES = \
-    traffic_top/traffic_top.cc
+	traffic_top/traffic_top.cc \
+	shared/rpc/IPCSocketClient.cc
 
 traffic_top_traffic_top_LDADD = \
-	$(top_builddir)/src/records/librecords_p.a \
-	$(top_builddir)/mgmt/libmgmt_p.la \
-	$(top_builddir)/proxy/shared/libUglyLogStubs.a \
 	$(top_builddir)/iocore/eventsystem/libinkevent.a \
-	$(top_builddir)/mgmt/api/libtsmgmt.la \
 	$(top_builddir)/src/tscore/libtscore.la \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
 	@CURL_LIBS@ \
 	@CURSES_LIBS@ \
-	@HWLOC_LIBS@
-
+	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ @YAMLCPP_LIBS@
 endif
diff --git a/src/traffic_top/stats.h b/src/traffic_top/stats.h
index 70965db..dfb7870 100644
--- a/src/traffic_top/stats.h
+++ b/src/traffic_top/stats.h
@@ -20,6 +20,8 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 */
+#pragma once
+
 #if HAS_CURL
 #include <curl/curl.h>
 #endif
@@ -30,7 +32,10 @@
 #include <fcntl.h>
 #include <cinttypes>
 #include <sys/time.h>
-#include "mgmtapi.h"
+
+#include "shared/rpc/RPCRequests.h"
+#include "shared/rpc/RPCClient.h"
+#include "shared/rpc/yaml_codecs.h"
 
 struct LookupItem {
   LookupItem(const char *s, const char *n, const int t) : pretty(s), name(n), numerator(""), denominator(""), type(t) {}
@@ -57,6 +62,19 @@
 const char end[]       = "\",\n";
 }; // namespace constant
 
+// Convenient definitions
+namespace detail
+{
+/// This is a convenience class to abstract the metric params. It makes it less verbose to add  a metric info object inside the
+/// record lookup object.
+struct MetricParam : shared::rpc::RecordLookupRequest::Params {
+  MetricParam(std::string name)
+    : // not regex
+      shared::rpc::RecordLookupRequest::Params{std::move(name), shared::rpc::NOT_REGEX, shared::rpc::METRIC_REC_TYPES}
+  {
+  }
+};
+} // namespace detail
 //----------------------------------------------------------------------------
 class Stats
 {
@@ -271,7 +289,6 @@
   getStats()
   {
     if (_url == "") {
-      int64_t value = 0;
       if (_old_stats != nullptr) {
         delete _old_stats;
         _old_stats = nullptr;
@@ -282,36 +299,22 @@
       gettimeofday(&_time, nullptr);
       double now = _time.tv_sec + (double)_time.tv_usec / 1000000;
 
+      // We will lookup for all the metrics on one single request.
+      shared::rpc::RecordLookupRequest request;
+
       for (map<string, LookupItem>::const_iterator lookup_it = lookup_table.begin(); lookup_it != lookup_table.end(); ++lookup_it) {
         const LookupItem &item = lookup_it->second;
 
         if (item.type == 1 || item.type == 2 || item.type == 5 || item.type == 8) {
-          if (strcmp(item.pretty, "Version") == 0) {
-            // special case for Version information
-            TSString strValue = nullptr;
-            if (TSRecordGetString(item.name, &strValue) == TS_ERR_OKAY) {
-              string key     = item.name;
-              (*_stats)[key] = strValue;
-              TSfree(strValue);
-            } else {
-              fprintf(stderr, "Error getting stat: %s when calling TSRecordGetString() failed: file \"%s\", line %d\n\n", item.name,
-                      __FILE__, __LINE__);
-              abort();
-            }
-          } else {
-            if (TSRecordGetInt(item.name, &value) != TS_ERR_OKAY) {
-              fprintf(stderr, "Error getting stat: %s when calling TSRecordGetInt() failed: file \"%s\", line %d\n\n", item.name,
-                      __FILE__, __LINE__);
-              abort();
-            }
-            string key = item.name;
-            char buffer[32];
-            snprintf(buffer, sizeof(buffer), "%" PRId64, value);
-            string foo     = buffer;
-            (*_stats)[key] = foo;
-          }
+          // Add records names to the rpc request.
+          request.emplace_rec(detail::MetricParam{item.name});
         }
       }
+      // query the rpc node.
+      if (auto const &error = fetch_and_fill_stats(request, _stats); !error.empty()) {
+        fprintf(stderr, "Error getting stats from the RPC node:\n%s", error.c_str());
+        abort();
+      }
       _old_time  = _now;
       _now       = now;
       _time_diff = _now - _old_time;
@@ -383,7 +386,7 @@
   getStat(const string &key, string &value)
   {
     map<string, LookupItem>::const_iterator lookup_it = lookup_table.find(key);
-    assert(lookup_it != lookup_table.end());
+    ink_assert(lookup_it != lookup_table.end());
     const LookupItem &item = lookup_it->second;
 
     map<string, string>::const_iterator stats_it = _stats->find(item.name);
@@ -401,7 +404,7 @@
     value = 0;
 
     map<string, LookupItem>::const_iterator lookup_it = lookup_table.find(key);
-    assert(lookup_it != lookup_table.end());
+    ink_assert(lookup_it != lookup_table.end());
     const LookupItem &item = lookup_it->second;
     prettyName             = item.pretty;
     if (overrideType != 0) {
@@ -543,6 +546,51 @@
     return std::make_pair(s, i);
   }
 
+  /// Invoke the remote server and fill the responses into the stats map.
+  std::string
+  fetch_and_fill_stats(shared::rpc::RecordLookupRequest const &request, std::map<std::string, std::string> *stats) noexcept
+  {
+    namespace rpc = shared::rpc;
+
+    if (stats == nullptr) {
+      return "Invalid stats parameter, it shouldn't be null.";
+    }
+    try {
+      rpc::RPCClient rpcClient;
+
+      // invoke the rpc.
+      auto const &rpcResponse = rpcClient.invoke<>(request);
+
+      if (!rpcResponse.is_error()) {
+        auto const &records = rpcResponse.result.as<rpc::RecordLookUpResponse>();
+
+        // we check if we got some specific record error, if any we report it.
+        if (records.errorList.size()) {
+          std::stringstream ss;
+
+          for (auto const &err : records.errorList) {
+            ss << err;
+            ss << "----\n";
+          }
+          return ss.str();
+        } else {
+          // No records error, so we are good to fill the list
+          for (auto &&recordInfo : records.recordList) {
+            (*stats)[recordInfo.name] = recordInfo.currentValue;
+          }
+        }
+      } else {
+        // something didn't work inside the RPC server.
+        std::stringstream ss;
+        ss << rpcResponse.error.as<rpc::JSONRPCError>();
+        return ss.str();
+      }
+    } catch (std::exception const &ex) {
+      return {ex.what()};
+    }
+    return {}; // no error
+  }
+
   map<string, string> *_stats;
   map<string, string> *_old_stats;
   map<string, LookupItem> lookup_table;
diff --git a/src/traffic_top/traffic_top.cc b/src/traffic_top/traffic_top.cc
index 2ac6b2a..c20c74a 100644
--- a/src/traffic_top/traffic_top.cc
+++ b/src/traffic_top/traffic_top.cc
@@ -55,8 +55,7 @@
 
 #include "tscore/I_Layout.h"
 #include "tscore/ink_args.h"
-#include "records/I_RecProcess.h"
-#include "RecordsConfig.h"
+#include "tscore/I_Version.h"
 #include "tscore/runroot.h"
 
 using namespace std;
@@ -411,30 +410,14 @@
 
   runroot_handler(argv);
   Layout::create();
-  RecProcessInit(RECM_STAND_ALONE, nullptr /* diags */);
-  LibRecordsConfigInit();
 
-  switch (n_file_arguments) {
-  case 0: {
-    ats_scoped_str rundir(RecConfigReadRuntimeDir());
-
-    TSMgmtError err = TSInit(rundir, static_cast<TSInitOptionT>(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS));
-    if (err != TS_ERR_OKAY) {
-      fprintf(stderr, "Error: connecting to local manager: %s\n", TSGetErrorMessage(err));
-      exit(1);
-    }
-    break;
-  }
-
-  case 1:
+  if (n_file_arguments == 1) {
 #if HAS_CURL
     url = file_arguments[0];
 #else
     usage(argument_descriptions, countof(argument_descriptions), USAGE);
 #endif
-    break;
-
-  default:
+  } else if (n_file_arguments > 1) {
     usage(argument_descriptions, countof(argument_descriptions), USAGE);
   }
 
diff --git a/src/traffic_via/traffic_via.cc b/src/traffic_via/traffic_via.cc
index 65fb617..33df12e 100644
--- a/src/traffic_via/traffic_via.cc
+++ b/src/traffic_via/traffic_via.cc
@@ -24,7 +24,6 @@
 #include "tscore/ink_platform.h"
 #include "tscore/ink_args.h"
 #include "tscore/I_Version.h"
-#include "mgmtapi.h"
 #include <cstdio>
 #include <cstring>
 #include <iostream>
@@ -219,7 +218,7 @@
 }
 
 // Check validity of via header and then decode it
-static TSMgmtError
+static bool
 decodeViaHeader(std::string_view text)
 {
   // Via header inside square brackets
@@ -228,7 +227,7 @@
     text.remove_suffix(1);
   }
   if (text.empty()) {
-    return TS_ERR_FAIL;
+    return false;
   }
 
   printf("Via header is [%.*s], Length is %zu\n", int(text.size()), text.data(), text.size());
@@ -243,18 +242,18 @@
   if (text.size() == 22 || text.size() == 6) {
     // Decode via header
     printViaHeader(text);
-    return TS_ERR_OKAY;
+    return true;
   }
   // Invalid header size, come out.
   printf("\nInvalid VIA header. VIA header length should be 6 or 22 characters\n");
   printf("Valid via header format is "
          "[u<client-stuff>c<cache-lookup-stuff>s<server-stuff>f<cache-fill-stuff>p<proxy-stuff>e<error-codes>:t<tunneling-info>c<"
          "cache type><cache-lookup-result>p<parent-proxy-conn-info>s<server-conn-info>]\n");
-  return TS_ERR_FAIL;
+  return false;
 }
 
 // Read user input from stdin
-static TSMgmtError
+static bool
 filterViaHeader()
 {
   const pcre *compiledReg;
@@ -273,7 +272,7 @@
 
   if (compiledReg == nullptr) {
     printf("PCRE regex compilation failed with error %s at offset %d\n", err, errOffset);
-    return TS_ERR_FAIL;
+    return false;
   }
 
   // Read all lines from stdin
@@ -299,13 +298,13 @@
       decodeViaHeader(match);
     }
   }
-  return TS_ERR_OKAY;
+  return true;
 }
 
 int
 main(int /* argc ATS_UNUSED */, const char **argv)
 {
-  TSMgmtError status;
+  bool opStatus;
 
   // build the application information structure
   appVersionInfo.setup(PACKAGE_NAME, "traffic_via", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
@@ -321,12 +320,12 @@
   for (unsigned i = 0; i < n_file_arguments; ++i) {
     if (strcmp(file_arguments[i], "-") == 0) {
       // Filter arguments provided from stdin
-      status = filterViaHeader();
+      opStatus = filterViaHeader();
     } else {
-      status = decodeViaHeader(std::string_view{file_arguments[i], strlen(file_arguments[i])});
+      opStatus = decodeViaHeader(std::string_view{file_arguments[i], strlen(file_arguments[i])});
     }
 
-    if (status != TS_ERR_OKAY) {
+    if (!opStatus) {
       return 1;
     }
   }
diff --git a/src/tscore/ArgParser.cc b/src/tscore/ArgParser.cc
index b7cb4af..ab233f9 100644
--- a/src/tscore/ArgParser.cc
+++ b/src/tscore/ArgParser.cc
@@ -32,6 +32,7 @@
 #include <sysexits.h>
 
 std::string global_usage;
+std::string description;
 std::string parser_program_name;
 std::string default_command;
 
@@ -91,6 +92,10 @@
 void
 ArgParser::Command::help_message(std::string_view err) const
 {
+  if (!description.empty()) {
+    std::cout << description << '\n';
+  }
+
   if (!err.empty()) {
     std::cout << "Error: " << err << std::endl;
   }
@@ -98,6 +103,7 @@
   if (global_usage.size() > 0) {
     std::cout << "\nUsage: " + global_usage << std::endl;
   }
+
   // output subcommands
   std::cout << "\nCommands ---------------------- Description -----------------------" << std::endl;
   std::string prefix = "";
@@ -209,6 +215,11 @@
   return _error_msg;
 }
 
+void
+ArgParser::add_description(std::string const &descr)
+{
+  description = descr;
+}
 //=========================== Command class ================================
 ArgParser::Command::Command() {}
 
diff --git a/src/tscore/BaseLogFile.cc b/src/tscore/BaseLogFile.cc
index 0882c2b..1d9fe67 100644
--- a/src/tscore/BaseLogFile.cc
+++ b/src/tscore/BaseLogFile.cc
@@ -391,9 +391,7 @@
  * instance of Diags is ready, we cannot simply call something like Debug().
  * However, we still need to log the creation of BaseLogFile, since the
  * information is still useful. This function will print out log messages
- * into traffic.out if we happen to be bootstrapping Diags. Since
- * traffic_manager redirects stdout/stderr into traffic.out, that
- * redirection is inherited by way of exec()/fork() all the way here.
+ * into traffic.out if we happen to be bootstrapping Diags.
  *
  * TODO use Debug() for non bootstrap instances
  */
diff --git a/src/tscore/BufferWriterFormat.cc b/src/tscore/BufferWriterFormat.cc
index b15faf6..9c4eb3a 100644
--- a/src/tscore/BufferWriterFormat.cc
+++ b/src/tscore/BufferWriterFormat.cc
@@ -69,6 +69,8 @@
 
 namespace ts
 {
+template <typename T> using MemSpan = swoc::MemSpan<T>;
+
 const BWFSpec BWFSpec::DEFAULT;
 
 const BWFSpec::Property BWFSpec::_prop;
diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am
index 79affbc..96174d0 100644
--- a/src/tscore/Makefile.am
+++ b/src/tscore/Makefile.am
@@ -35,21 +35,24 @@
 
 AM_CPPFLAGS += \
 	$(iocore_include_dirs) \
+        @SWOC_INCLUDES@ \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/lib \
 	$(TS_INCLUDES) \
 	@YAMLCPP_INCLUDES@
 
-libtscore_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_VERSION@ @YAMLCPP_LDFLAGS@
+libtscore_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_VERSION@ @SWOC_LDFLAGS@ @YAMLCPP_LDFLAGS@
 libtscore_la_LIBADD = \
 	$(top_builddir)/src/tscpp/util/libtscpputil.la \
-	@HWLOC_LIBS@ \
+	@SWOC_LIBS@ \
 	@LIBOBJS@ \
 	@LIBPCRE@ \
 	@OPENSSL_LIBS@ \
 	@LIBRESOLV@ \
 	@LIBCAP@ \
+	@HWLOC_LIBS@ \
 	@YAMLCPP_LIBS@ \
+	@SWOC_LIBS@ \
 	-lc
 
 libtscore_la_SOURCES = \
@@ -149,23 +152,23 @@
 	LSAN_OPTIONS='detect_leaks=0' ./CompileParseRules
 
 test_atomic_SOURCES = test_atomic.cc
-test_atomic_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @LIBPCRE@
+test_atomic_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @SWOC_LIBSS@ @LIBPCRE@
 
 test_freelist_SOURCES = test_freelist.cc
-test_freelist_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @LIBPCRE@
+test_freelist_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @SWOC_LIBS@ @LIBPCRE@
 
 test_geometry_SOURCES = test_geometry.cc
-test_geometry_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @LIBPCRE@
+test_geometry_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @SWOC_LIBS@ @LIBPCRE@ -lstdc++
 
 test_X509HostnameValidator_CPPFLAGS = $(AM_CPPFLAGS) -I$(abs_top_srcdir)/tests/include
-test_X509HostnameValidator_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @LIBPCRE@ @OPENSSL_LIBS@
+test_X509HostnameValidator_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la @SWOC_LIBS@ @LIBPCRE@ @OPENSSL_LIBS@
 test_X509HostnameValidator_SOURCES = unit_tests/test_X509HostnameValidator.cc
 
 test_tscore_CPPFLAGS = $(AM_CPPFLAGS)\
 	-I$(abs_top_srcdir)/tests/include
 
 test_tscore_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS)
-test_tscore_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la $(top_builddir)/iocore/eventsystem/libinkevent.a @OPENSSL_LIBS@
+test_tscore_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la $(top_builddir)/iocore/eventsystem/libinkevent.a @SWOC_LIBS@ @OPENSSL_LIBS@
 test_tscore_SOURCES = \
 	unit_tests/unit_test_main.cc \
 	unit_tests/test_AcidPtr.cc \
@@ -191,7 +194,6 @@
 	unit_tests/test_PriorityQueue.cc \
 	unit_tests/test_Ptr.cc \
 	unit_tests/test_Regex.cc \
-	unit_tests/test_Scalar.cc \
 	unit_tests/test_scoped_resource.cc \
 	unit_tests/test_Throttler.cc \
 	unit_tests/test_Tokenizer.cc \
diff --git a/src/tscore/MemArena.cc b/src/tscore/MemArena.cc
index 877cb97..4010d5e 100644
--- a/src/tscore/MemArena.cc
+++ b/src/tscore/MemArena.cc
@@ -29,6 +29,9 @@
 #include "tscore/ink_assert.h"
 
 using namespace ts;
+template <typename T> using MemSpan = swoc::MemSpan<T>;
+using swoc::round_down;
+using swoc::round_up;
 
 void
 MemArena::Block::operator delete(void *ptr)
diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc
index 815f106..50e13e3 100644
--- a/src/tscore/ink_inet.cc
+++ b/src/tscore/ink_inet.cc
@@ -50,10 +50,10 @@
 const std::string_view IP_PROTO_TAG_HTTP_1_0("http/1.0"sv);
 const std::string_view IP_PROTO_TAG_HTTP_1_1("http/1.1"sv);
 const std::string_view IP_PROTO_TAG_HTTP_2_0("h2"sv);         // HTTP/2 over TLS
-const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq-29"sv);     // HTTP/0.9 over QUIC
-const std::string_view IP_PROTO_TAG_HTTP_3("h3-29"sv);        // HTTP/3 over QUIC
-const std::string_view IP_PROTO_TAG_HTTP_QUIC_D27("hq-27"sv); // HTTP/0.9 over QUIC (draft-27)
-const std::string_view IP_PROTO_TAG_HTTP_3_D27("h3-27"sv);    // HTTP/3 over QUIC (draft-27)
+const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq"sv);        // HTTP/0.9 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_3("h3"sv);           // HTTP/3 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_QUIC_D29("hq-29"sv); // HTTP/0.9 over QUIC (draft-29)
+const std::string_view IP_PROTO_TAG_HTTP_3_D29("h3-29"sv);    // HTTP/3 over QUIC (draft-29)
 
 const std::string_view UNIX_PROTO_TAG{"unix"sv};
 
diff --git a/src/tscore/unit_tests/test_BufferWriterFormat.cc b/src/tscore/unit_tests/test_BufferWriterFormat.cc
index 76d9708..26bec8b 100644
--- a/src/tscore/unit_tests/test_BufferWriterFormat.cc
+++ b/src/tscore/unit_tests/test_BufferWriterFormat.cc
@@ -28,7 +28,7 @@
 #include <netinet/in.h>
 #include "tscore/BufferWriter.h"
 #include "tscore/bwf_std_format.h"
-#include "tscpp/util/MemSpan.h"
+#include "swoc/MemSpan.h"
 #include "tscore/ink_config.h"
 #if TS_ENABLE_FIPS == 0
 #include "tscore/MD5.h"
@@ -182,22 +182,22 @@
   bw.print("{}", char_ptr);
   REQUIRE(bw.view() == "good");
 
-  ts::MemSpan span{ptr, 0x200};
+  swoc::MemSpan span{ptr, 0x200};
   bw.reduce(0);
   bw.print("{}", span);
   REQUIRE(bw.view() == "0x200@0xbadd0956");
 
   bw.reduce(0);
-  bw.print("{:x}", ts::MemSpan(char_ptr, 4));
+  bw.print("{:x}", swoc::MemSpan(char_ptr, 4));
   REQUIRE(bw.view() == "676f6f64");
   bw.reduce(0);
-  bw.print("{:#x}", ts::MemSpan(char_ptr, 4));
+  bw.print("{:#x}", swoc::MemSpan(char_ptr, 4));
   REQUIRE(bw.view() == "0x676f6f64");
   bw.reduce(0);
-  bw.print("{:x}", ts::MemSpan<void>(char_ptr, 4));
+  bw.print("{:x}", swoc::MemSpan<void>(char_ptr, 4));
   REQUIRE(bw.view() == "676f6f64");
   bw.reduce(0);
-  bw.print("{:#x}", ts::MemSpan<void>(char_ptr, 4));
+  bw.print("{:#x}", swoc::MemSpan<void>(char_ptr, 4));
   REQUIRE(bw.view() == "0x676f6f64");
 
   std::string_view sv{"abc123"};
diff --git a/src/tscore/unit_tests/test_MemArena.cc b/src/tscore/unit_tests/test_MemArena.cc
index 16a6276..d704be5 100644
--- a/src/tscore/unit_tests/test_MemArena.cc
+++ b/src/tscore/unit_tests/test_MemArena.cc
@@ -25,7 +25,7 @@
 
 #include <string_view>
 #include "tscore/MemArena.h"
-using ts::MemSpan;
+using swoc::MemSpan;
 using ts::MemArena;
 using namespace std::literals;
 
@@ -129,7 +129,7 @@
 
   ts::MemArena arena{256};
   REQUIRE(arena.size() == 0);
-  ts::MemSpan<char> s = arena.alloc(56).rebind<char>();
+  swoc::MemSpan<char> s = arena.alloc(56).rebind<char>();
   REQUIRE(arena.size() == 56);
   void *ptr = s.begin();
 
@@ -141,8 +141,8 @@
   arena.freeze(128);
   REQUIRE(arena.contains((char *)ptr));
   REQUIRE(arena.contains((char *)ptr + 100));
-  ts::MemSpan<char> s2 = arena.alloc(10).rebind<char>();
-  void *ptr2           = s2.begin();
+  swoc::MemSpan<char> s2 = arena.alloc(10).rebind<char>();
+  void *ptr2             = s2.begin();
   REQUIRE(arena.contains((char *)ptr));
   REQUIRE(arena.contains((char *)ptr2));
   REQUIRE(arena.allocated_size() == 56 + 10);
@@ -183,10 +183,10 @@
 TEST_CASE("MemArena large alloc", "[libts][MemArena]")
 {
   ts::MemArena arena;
-  ts::MemSpan s = arena.alloc(4000);
+  swoc::MemSpan s = arena.alloc(4000);
   REQUIRE(s.size() == 4000);
 
-  ts::MemSpan<void> s_a[10];
+  swoc::MemSpan<void> s_a[10];
   s_a[0] = arena.alloc(100);
   s_a[1] = arena.alloc(200);
   s_a[2] = arena.alloc(300);
@@ -210,9 +210,9 @@
 TEST_CASE("MemArena block allocation", "[libts][MemArena]")
 {
   ts::MemArena arena{64};
-  ts::MemSpan<char> s  = arena.alloc(32).rebind<char>();
-  ts::MemSpan<char> s2 = arena.alloc(16).rebind<char>();
-  ts::MemSpan<char> s3 = arena.alloc(16).rebind<char>();
+  swoc::MemSpan<char> s  = arena.alloc(32).rebind<char>();
+  swoc::MemSpan<char> s2 = arena.alloc(16).rebind<char>();
+  swoc::MemSpan<char> s3 = arena.alloc(16).rebind<char>();
 
   REQUIRE(s.size() == 32);
   REQUIRE(arena.allocated_size() == 64);
diff --git a/src/tscore/unit_tests/test_Scalar.cc b/src/tscore/unit_tests/test_Scalar.cc
deleted file mode 100644
index d38c2e8..0000000
--- a/src/tscore/unit_tests/test_Scalar.cc
+++ /dev/null
@@ -1,301 +0,0 @@
-/** @file
-
-    Scalar unit 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 <catch.hpp>
-
-#include "tscore/Scalar.h"
-
-using Bytes      = ts::Scalar<1, off_t>;
-using Paragraphs = ts::Scalar<16, off_t>;
-using KB         = ts::Scalar<1024, off_t>;
-using MB         = ts::Scalar<KB::SCALE * 1024, off_t>;
-
-TEST_CASE("Scalar", "[libts][Scalar]")
-{
-  constexpr static int SCALE   = 4096;
-  constexpr static int SCALE_1 = 8192;
-  constexpr static int SCALE_2 = 512;
-
-  using PageSize = ts::Scalar<SCALE>;
-
-  PageSize pg1(1);
-  REQUIRE(pg1.count() == 1);
-  REQUIRE(pg1.value() == SCALE);
-
-  using Size_1 = ts::Scalar<SCALE_1>;
-  using Size_2 = ts::Scalar<SCALE_2>;
-
-  Size_2 sz_a(2);
-  Size_2 sz_b(57);
-  Size_2 sz_c(SCALE_1 / SCALE_2);
-  Size_2 sz_d(29 * SCALE_1 / SCALE_2);
-
-  Size_1 sz = ts::round_up(sz_a);
-  REQUIRE(sz.count() == 1);
-  sz = ts::round_down(sz_a);
-  REQUIRE(sz.count() == 0);
-
-  sz = ts::round_up(sz_b);
-  REQUIRE(sz.count() == 4);
-  sz = ts::round_down(sz_b);
-  REQUIRE(sz.count() == 3);
-
-  sz = ts::round_up(sz_c);
-  REQUIRE(sz.count() == 1);
-  sz = ts::round_down(sz_c);
-  REQUIRE(sz.count() == 1);
-
-  sz = ts::round_up(sz_d);
-  REQUIRE(sz.count() == 29);
-  sz = ts::round_down(sz_d);
-  REQUIRE(sz.count() == 29);
-
-  sz.assign(119);
-  sz_b = sz; // Should be OK because SCALE_1 is an integer multiple of SCALE_2
-  //  sz = sz_b; // Should not compile.
-  REQUIRE(sz_b.count() == 119 * (SCALE_1 / SCALE_2));
-
-  // Test generic rounding.
-  REQUIRE(120 == ts::round_up<10>(118));
-  REQUIRE(120 == ts::round_up<10>(120));
-  REQUIRE(130 == ts::round_up<10>(121));
-
-  REQUIRE(110 == ts::round_down<10>(118));
-  REQUIRE(120 == ts::round_down<10>(120));
-  REQUIRE(120 == ts::round_down<10>(121));
-
-  REQUIRE(1200 == ts::round_up<100>(1108));
-  REQUIRE(1200 == ts::round_up<100>(1200));
-  REQUIRE(1300 == ts::round_up<100>(1201));
-
-  REQUIRE(100 == ts::round_down<100>(118));
-  REQUIRE(1100 == ts::round_down<100>(1108));
-  REQUIRE(1200 == ts::round_down<100>(1200));
-  REQUIRE(1200 == ts::round_down<100>(1208));
-}
-
-TEST_CASE("Scalar Factors", "[libts][Scalar][factors]")
-{
-  constexpr static int SCALE_1 = 30;
-  constexpr static int SCALE_2 = 20;
-
-  using Size_1 = ts::Scalar<SCALE_1>;
-  using Size_2 = ts::Scalar<SCALE_2>;
-
-  Size_2 sz_a(2);
-  Size_2 sz_b(97);
-
-  Size_1 sz = round_up(sz_a);
-  REQUIRE(sz.count() == 2);
-  sz = round_down(sz_a);
-  REQUIRE(sz.count() == 1);
-
-  sz = ts::round_up(sz_b);
-  REQUIRE(sz.count() == 65);
-  sz = ts::round_down(sz_b);
-  REQUIRE(sz.count() == 64);
-
-  ts::Scalar<9> m_9;
-  ts::Scalar<4> m_4, m_test;
-
-  m_9.assign(95);
-  //  m_4 = m_9; // Should fail to compile with static assert.
-  //  m_9 = m_4; // Should fail to compile with static assert.
-
-  m_4 = ts::round_up(m_9);
-  REQUIRE(m_4.count() == 214);
-  m_4 = ts::round_down(m_9);
-  REQUIRE(m_4.count() == 213);
-
-  m_4.assign(213);
-  m_9 = ts::round_up(m_4);
-  REQUIRE(m_9.count() == 95);
-  m_9 = ts::round_down(m_4);
-  REQUIRE(m_9.count() == 94);
-
-  m_test = m_4; // Verify assignment of identical scale values compiles.
-  REQUIRE(m_test.count() == 213);
-}
-
-TEST_CASE("Scalar Arithmetic", "[libts][Scalar][arithmetic]")
-{
-  using KBytes  = ts::Scalar<1024>;
-  using KiBytes = ts::Scalar<1024, long int>;
-  using Bytes   = ts::Scalar<1, int64_t>;
-  using MBytes  = ts::Scalar<1024 * KBytes::SCALE>;
-
-  Bytes bytes(96);
-  KBytes kbytes(2);
-  MBytes mbytes(5);
-
-  Bytes z1 = ts::round_up(bytes + 128);
-  REQUIRE(z1.count() == 224);
-  KBytes z2 = kbytes + kbytes(3);
-  REQUIRE(z2.count() == 5);
-  Bytes z3(bytes);
-  z3 += kbytes;
-  REQUIRE(z3.value() == 2048 + 96);
-  MBytes z4 = mbytes;
-  z4.inc(5);
-  z2 += z4;
-  REQUIRE(z2.value() == (10 << 20) + (5 << 10));
-
-  z1.inc(128);
-  REQUIRE(z1.count() == 352);
-
-  z2.assign(2);
-  z1 = 3 * z2;
-  REQUIRE(z1.count() == 6144);
-  z1 *= 5;
-  REQUIRE(z1.count() == 30720);
-  z1 /= 3;
-  REQUIRE(z1.count() == 10240);
-
-  z2.assign(3148);
-  auto x = z2 + MBytes(1);
-  REQUIRE(x.scale() == z2.scale());
-  REQUIRE(x.count() == 4172);
-
-  z2 = ts::round_down(262150);
-  REQUIRE(z2.count() == 256);
-
-  z2 = ts::round_up(262150);
-  REQUIRE(z2.count() == 257);
-
-  KBytes q(ts::round_down(262150));
-  REQUIRE(q.count() == 256);
-
-  z2 += ts::round_up(97384);
-  REQUIRE(z2.count() == 353);
-
-  decltype(z2) a = ts::round_down(z2 + 167229);
-  REQUIRE(a.count() == 516);
-
-  KiBytes k(3148);
-  auto kx = k + MBytes(1);
-  REQUIRE(kx.scale() == k.scale());
-  REQUIRE(kx.count() == 4172);
-
-  k = ts::round_down(262150);
-  REQUIRE(k.count() == 256);
-
-  k = ts::round_up(262150);
-  REQUIRE(k.count() == 257);
-
-  KBytes kq(ts::round_down(262150));
-  REQUIRE(kq.count() == 256);
-
-  k += ts::round_up(97384);
-  REQUIRE(k.count() == 353);
-
-  decltype(k) ka = ts::round_down(k + 167229);
-  REQUIRE(ka.count() == 516);
-
-  using StoreBlocks = ts::Scalar<8 * KB::SCALE, off_t>;
-  using SpanBlocks  = ts::Scalar<127 * MB::SCALE, off_t>;
-
-  StoreBlocks store_b(80759700);
-  SpanBlocks span_b(4968);
-  SpanBlocks delta(1);
-
-  REQUIRE(store_b < span_b);
-  REQUIRE(span_b < store_b + delta);
-  store_b += delta;
-  REQUIRE(span_b < store_b);
-
-  static const off_t N = 7 * 1024;
-  Bytes b(N + 384);
-  KB kb(round_down(b));
-
-  REQUIRE(kb == N);
-  REQUIRE(kb < N + 1);
-  REQUIRE(kb > N - 1);
-
-  REQUIRE(kb < b);
-  REQUIRE(kb <= b);
-  REQUIRE(b > kb);
-  REQUIRE(b >= kb);
-
-  ++kb;
-
-  REQUIRE(b < kb);
-  REQUIRE(b <= kb);
-  REQUIRE(kb > b);
-  REQUIRE(kb >= b);
-}
-
-#if 0
-struct KBytes_tag {
-  static std::string const label;
-};
-std::string const KBytes_tag::label(" bytes");
-
-void
-Test_IO()
-{
-  typedef ts::Scalar<1024, long int, KBytes_tag> KBytes;
-  typedef ts::Scalar<1024, int> KiBytes;
-
-  KBytes x(12);
-  KiBytes y(12);
-
-  std::cout << "Testing" << std::endl;
-  std::cout << "x is " << x << std::endl;
-  std::cout << "y is " << y << std::endl;
-}
-
-void
-test_Compile()
-{
-  // These tests aren't normally run, they exist to detect compiler issues.
-
-  typedef ts::Scalar<1024, short> KBytes;
-  typedef ts::Scalar<1024, int> KiBytes;
-  int delta = 10;
-
-  KBytes x(12);
-  KiBytes y(12);
-
-  if (x > 12) {
-    std::cout << "Operator > works" << std::endl;
-  }
-  if (y > 12) {
-    std::cout << "Operator > works" << std::endl;
-  }
-
-  (void)(x.inc(10));
-  (void)(x.inc(static_cast<int>(10)));
-  (void)(x.inc(static_cast<long int>(10)));
-  (void)(x.inc(delta));
-  (void)(y.inc(10));
-  (void)(y.inc(static_cast<int>(10)));
-  (void)(y.inc(static_cast<long int>(10)));
-  (void)(y.inc(delta));
-
-  (void)(x.dec(10));
-  (void)(x.dec(static_cast<int>(10)));
-  (void)(x.dec(static_cast<long int>(10)));
-  (void)(x.dec(delta));
-}
-
-#endif
diff --git a/src/tscpp/api/Makefile.am b/src/tscpp/api/Makefile.am
index 1232e92..bff43e1 100644
--- a/src/tscpp/api/Makefile.am
+++ b/src/tscpp/api/Makefile.am
@@ -19,7 +19,7 @@
 
 lib_LTLIBRARIES = libtscppapi.la
 
-libtscppapi_la_CPPFLAGS = $(AM_CPPFLAGS) -I $(abs_top_srcdir)/include
+libtscppapi_la_CPPFLAGS = $(AM_CPPFLAGS) -I $(abs_top_srcdir)/include @SWOC_INCLUDES@
 
 libtscppapi_la_LDFLAGS=-lz -lpthread -version-info @TS_LIBTOOL_VERSION@
 
diff --git a/src/tscpp/api/TransformationPlugin.cc b/src/tscpp/api/TransformationPlugin.cc
index 547dc7a..6fb3816 100644
--- a/src/tscpp/api/TransformationPlugin.cc
+++ b/src/tscpp/api/TransformationPlugin.cc
@@ -396,9 +396,13 @@
   if (state_->type_ == REQUEST_TRANSFORMATION) {
     state_->request_xform_output_.append(data.data(), data.length());
     return data.size();
-  } else if (state_->type_ == SINK_TRANSFORMATION) {
-    LOG_DEBUG("produce TransformationPlugin=%p tshttptxn=%p : This is a sink transform. Not producing any output", this,
-              state_->txn_);
+  } else if (state_->type_ == CLIENT_RESPONSE_SINK_TRANSFORMATION) {
+    LOG_DEBUG("produce TransformationPlugin=%p tshttptxn=%p : This is a client response sink transform. Not producing any output",
+              this, state_->txn_);
+    return 0;
+  } else if (state_->type_ == CLIENT_REQUEST_SINK_TRANSFORMATION) {
+    LOG_DEBUG("produce TransformationPlugin=%p tshttptxn=%p : This is a client request sink transform. Not producing any output",
+              this, state_->txn_);
     return 0;
   } else {
     return doProduce(data);
@@ -408,7 +412,7 @@
 size_t
 TransformationPlugin::setOutputComplete()
 {
-  if (state_->type_ == SINK_TRANSFORMATION) {
+  if (state_->type_ == CLIENT_RESPONSE_SINK_TRANSFORMATION || state_->type_ == CLIENT_REQUEST_SINK_TRANSFORMATION) {
     // There's no output stream for a sink transform, so we do nothing
     //
     // Warning: don't try to shutdown the VConn, since the default implementation (DummyVConnection)
diff --git a/src/tscpp/api/utils_internal.cc b/src/tscpp/api/utils_internal.cc
index a4da599..783ddb8 100644
--- a/src/tscpp/api/utils_internal.cc
+++ b/src/tscpp/api/utils_internal.cc
@@ -235,8 +235,10 @@
     return TS_HTTP_RESPONSE_TRANSFORM_HOOK;
   case TransformationPlugin::REQUEST_TRANSFORMATION:
     return TS_HTTP_REQUEST_TRANSFORM_HOOK;
-  case TransformationPlugin::SINK_TRANSFORMATION:
+  case TransformationPlugin::CLIENT_RESPONSE_SINK_TRANSFORMATION:
     return TS_HTTP_RESPONSE_CLIENT_HOOK;
+  case TransformationPlugin::CLIENT_REQUEST_SINK_TRANSFORMATION:
+    return TS_HTTP_REQUEST_CLIENT_HOOK;
   default:
     assert(false); // shouldn't happen, let's catch it early
     break;
diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am
index b7ddf52..66dffc2 100644
--- a/src/tscpp/util/Makefile.am
+++ b/src/tscpp/util/Makefile.am
@@ -24,22 +24,22 @@
 
 lib_LTLIBRARIES = libtscpputil.la
 
-AM_CPPFLAGS += -I$(abs_top_srcdir)/include
+AM_CPPFLAGS += -I$(abs_top_srcdir)/include @SWOC_INCLUDES@
 
-libtscpputil_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_VERSION@
+libtscpputil_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_VERSION@ @SWOC_LDFLAGS@
+libtscpputil_la_LIBADD = @SWOC_LIBS@
 
 libtscpputil_la_SOURCES = \
-	TextView.cc string_view_util.cc
+	ts_diags.cc ts_ip.cc TextView.cc
 
 test_tscpputil_CPPFLAGS = $(AM_CPPFLAGS)\
-	-I$(abs_top_srcdir)/tests/include
+	-I$(abs_top_srcdir)/tests/include @SWOC_INCLUDES@
 
 test_tscpputil_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS)
-test_tscpputil_LDADD = libtscpputil.la
+test_tscpputil_LDADD = libtscpputil.la @SWOC_LIBS@
 test_tscpputil_SOURCES = \
 	unit_tests/unit_test_main.cc \
 	unit_tests/test_LocalBuffer.cc \
-	unit_tests/test_MemSpan.cc \
 	unit_tests/test_PostScript.cc \
 	unit_tests/test_Strerror.cc \
 	unit_tests/test_TextView.cc \
diff --git a/src/tscpp/util/string_view_util.cc b/src/tscpp/util/string_view_util.cc
deleted file mode 100644
index 5cc213b..0000000
--- a/src/tscpp/util/string_view_util.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-/** @file
-
-    Utilities for @c std::string_view
-
-    @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 "tscpp/util/string_view_util.h"
-
-int
-memcmp(std::string_view const &lhs, std::string_view const &rhs)
-{
-  int zret = 0;
-  size_t n = rhs.size();
-
-  // Seems a bit ugly but size comparisons must be done anyway to get the memcmp args.
-  if (lhs.size() < rhs.size()) {
-    zret = 1;
-    n    = lhs.size();
-  } else if (lhs.size() > rhs.size()) {
-    zret = -1;
-  } else if (lhs.data() == rhs.data()) { // same memory, obviously equal.
-    return 0;
-  }
-
-  int r = ::memcmp(lhs.data(), rhs.data(), n);
-  return r ? r : zret;
-}
-
-int
-strcasecmp(const std::string_view &lhs, const std::string_view &rhs)
-{
-  int zret = 0;
-  size_t n = rhs.size();
-
-  // Seems a bit ugly but size comparisons must be done anyway to get the @c strncasecmp args.
-  if (lhs.size() < rhs.size()) {
-    zret = 1;
-    n    = lhs.size();
-  } else if (lhs.size() > rhs.size()) {
-    zret = -1;
-  } else if (lhs.data() == rhs.data()) { // the same memory, obviously equal.
-    return 0;
-  }
-
-  int r = ::strncasecmp(lhs.data(), rhs.data(), n);
-
-  return r ? r : zret;
-}
diff --git a/src/tscpp/util/ts_diags.cc b/src/tscpp/util/ts_diags.cc
new file mode 100644
index 0000000..78e6583
--- /dev/null
+++ b/src/tscpp/util/ts_diags.cc
@@ -0,0 +1,33 @@
+/** @file Support for common diagnostics between core, plugins, and libswoc.
+
+  This enables specifying the set of methods usable by a user agent based on the remove IP address
+  for a user agent connection.
+
+  @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 "tscpp/util/ts_diag_levels.h"
+#include "tscpp/util/ts_errata.h"
+
+static const bool INITIALIZED = []() -> bool {
+  swoc::Errata::DEFAULT_SEVERITY = ERRATA_ERROR;
+  swoc::Errata::FAILURE_SEVERITY = ERRATA_WARN;
+  swoc::Errata::SEVERITY_NAMES   = swoc::MemSpan<swoc::TextView const>(Severity_Names.data(), Severity_Names.size());
+  return true;
+}();
diff --git a/src/tscpp/util/ts_ip.cc b/src/tscpp/util/ts_ip.cc
new file mode 100644
index 0000000..01e8ae8
--- /dev/null
+++ b/src/tscpp/util/ts_ip.cc
@@ -0,0 +1,123 @@
+/** @file
+
+    IP address handling support.
+
+    @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 <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <tscore/ink_memory.h>
+
+#include "tscpp/util/ts_ip.h"
+
+namespace ts
+{
+IPAddrPair
+getbestaddrinfo(swoc::TextView name)
+{
+  // If @a name parses as a valid address, return it as that address.
+
+  if (swoc::IP4Addr addr; addr.load(name)) {
+    return addr;
+  }
+
+  if (swoc::IP6Addr addr; addr.load(name)) {
+    return addr;
+  }
+
+  // Presume it is a host name, make a copy to guarantee C string.
+  char *tmp = static_cast<char *>(alloca(name.size() + 1));
+  memcpy(tmp, name.data(), name.size());
+  tmp[name.size()] = 0;
+  name.assign(tmp, name.size());
+
+  // List of address types, in order of worst to best.
+  enum {
+    NA, // Not an (IP) Address.
+    LO, // Loopback.
+    LL, // Link Local.
+    PR, // Private.
+    MC, // Multicast.
+    GL  // Global.
+  } spot_type = NA,
+    ip4_type = NA, ip6_type = NA;
+  addrinfo ai_hints;
+  addrinfo *ai_result;
+  IPAddrPair zret;
+
+  // Do the resolution
+  ink_zero(ai_hints);
+  ai_hints.ai_family = AF_UNSPEC;
+  ai_hints.ai_flags  = AI_ADDRCONFIG;
+
+  if (0 == getaddrinfo(name.data(), nullptr, &ai_hints, &ai_result)) { // Walk the returned addresses and pick the "best".
+    for (addrinfo *ai_spot = ai_result; ai_spot; ai_spot = ai_spot->ai_next) {
+      swoc::IPAddr addr(ai_spot->ai_addr);
+      if (!addr.is_valid()) {
+        spot_type = NA;
+      } else if (addr.is_loopback()) {
+        spot_type = LO;
+      } else if (addr.is_link_local()) {
+        spot_type = LL;
+      } else if (addr.is_private()) {
+        spot_type = PR;
+      } else if (addr.is_multicast()) {
+        spot_type = MC;
+      } else {
+        spot_type = GL;
+      }
+
+      if (spot_type == NA) {
+        continue; // Next!
+      }
+
+      if (addr.is_ip4()) {
+        if (spot_type > ip4_type) {
+          zret     = addr.ip4();
+          ip4_type = spot_type;
+        }
+      } else if (addr.is_ip6()) {
+        if (spot_type > ip6_type) {
+          zret     = addr.ip6();
+          ip6_type = spot_type;
+        }
+      }
+    }
+
+    freeaddrinfo(ai_result); // free *after* the copy.
+  }
+
+  return zret;
+}
+
+IPSrvPair
+getbestsrvinfo(swoc::TextView src)
+{
+  swoc::TextView addr_text;
+  swoc::TextView port_text;
+  if (swoc::IPEndpoint::tokenize(src, &addr_text, &port_text)) {
+    in_port_t port = swoc::svtoi(port_text);
+    return IPSrvPair{ts::getbestaddrinfo(addr_text), port};
+  }
+  return {};
+}
+
+} // namespace ts
diff --git a/src/tscpp/util/unit_tests/test_MemSpan.cc b/src/tscpp/util/unit_tests/test_MemSpan.cc
deleted file mode 100644
index 7149e68..0000000
--- a/src/tscpp/util/unit_tests/test_MemSpan.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-/** @file
-
-    MemSpan unit tests.
-
-    @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 <iostream>
-#include "tscpp/util/MemSpan.h"
-#include "catch.hpp"
-
-using ts::MemSpan;
-
-TEST_CASE("MemSpan", "[libswoc][MemSpan]")
-{
-  int32_t idx[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
-  char buff[1024];
-
-  MemSpan<char> span(buff, sizeof(buff));
-  MemSpan<char> left = span.prefix(512);
-  REQUIRE(left.size() == 512);
-  REQUIRE(span.size() == 1024);
-  span.remove_prefix(512);
-  REQUIRE(span.size() == 512);
-  REQUIRE(left.end() == span.begin());
-
-  left.assign(buff, sizeof(buff));
-  span = left.suffix(768);
-  left.remove_suffix(768);
-  REQUIRE(left.end() == span.begin());
-  REQUIRE(left.size() + span.size() == 1024);
-
-  MemSpan<int32_t> idx_span(idx);
-  REQUIRE(idx_span.count() == 11);
-  REQUIRE(idx_span.size() == sizeof(idx));
-  REQUIRE(idx_span.data() == idx);
-
-  auto sp2 = idx_span.rebind<int16_t>();
-  REQUIRE(sp2.size() == idx_span.size());
-  REQUIRE(sp2.count() == 2 * idx_span.count());
-  REQUIRE(sp2[0] == 0);
-  REQUIRE(sp2[1] == 0);
-  // exactly one of { le, be } must be true.
-  bool le = sp2[2] == 1 && sp2[3] == 0;
-  bool be = sp2[2] == 0 && sp2[3] == 1;
-  REQUIRE(le != be);
-  auto idx2 = sp2.rebind<int32_t>(); // still the same if converted back to original?
-  REQUIRE(idx_span.is_same(idx2));
-
-  // Verify attempts to rebind on non-integral sized arrays fails.
-  span.assign(buff, 1022);
-  REQUIRE(span.size() == 1022);
-  REQUIRE(span.count() == 1022);
-  auto vs = span.rebind<void>();
-  REQUIRE_THROWS_AS(span.rebind<uint32_t>(), std::invalid_argument);
-  REQUIRE_THROWS_AS(vs.rebind<uint32_t>(), std::invalid_argument);
-
-  // Check for defaulting to a void rebind.
-  vs = span.rebind();
-  REQUIRE(vs.size() == 1022);
-
-  // Check for assignment to void.
-  vs = span;
-  REQUIRE(vs.size() == 1022);
-
-  // Test array constructors.
-  MemSpan<char> a{buff};
-  REQUIRE(a.size() == sizeof(buff));
-  REQUIRE(a.data() == buff);
-  float floats[] = {1.1, 2.2, 3.3, 4.4, 5.5};
-  MemSpan<float> fspan{floats};
-  REQUIRE(fspan.count() == 5);
-  REQUIRE(fspan[3] == 4.4f);
-  MemSpan<float> f2span{floats, floats + 5};
-  REQUIRE(fspan.data() == f2span.data());
-  REQUIRE(fspan.count() == f2span.count());
-  REQUIRE(fspan.is_same(f2span));
-};
diff --git a/tests/Makefile.am b/tests/Makefile.am
index cbea06c..f75e00b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -19,6 +19,7 @@
 
 noinst_LTLIBRARIES =
 noinst_PROGRAMS =
+pkglib_LTLIBRARIES =
 
 SUBDIRS =
 
@@ -39,6 +40,7 @@
 include gold_tests/timeout/Makefile.inc
 include gold_tests/tls/Makefile.inc
 include tools/plugins/Makefile.inc
+include gold_tests/jsonrpc/plugins/Makefile.inc
 
 TESTS = $(check_PROGRAMS)
 
diff --git a/tests/gold_tests/autest-site/jsonrpc.py b/tests/gold_tests/autest-site/jsonrpc.py
new file mode 100644
index 0000000..4bbaa36
--- /dev/null
+++ b/tests/gold_tests/autest-site/jsonrpc.py
@@ -0,0 +1,283 @@
+'''
+JSONRPC Request convenient class helper.
+'''
+#  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 typing.ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import typing
+from collections import OrderedDict
+import json
+import uuid
+
+
+class BaseRequestType(type):
+    '''
+    Base class for both, request and notifications
+    '''
+    def __getattr__(cls: typing.Callable, name: str) -> typing.Callable:
+        def attr_handler(*args: typing.Any, **kwargs: typing.Any) -> "Request":
+            return cls(name, *args, **kwargs)
+        return attr_handler
+
+
+class Notification(dict, metaclass=BaseRequestType):
+    '''
+    Convenient class to create JSONRPC objects(Notifications) and strings without the need to specify redundant information.
+
+    Examples:
+
+        Notification.foo_bar() = > {"jsonrpc": "2.0", "method": "foo_bar"}
+
+        Notification.foo_bar({"hello"="world"}) =>
+            {
+                    "jsonrpc": "2.0",
+                    "method": "foo_bar",
+                    "params": {
+                        "hello": "world"
+                    }
+                }
+
+        Notification.foo_bar(var={"hello":"world"}) =>
+            {
+                "jsonrpc": "2.0",
+                "method": "foo_bar",
+                "params": {
+                    "var": {
+                        "hello": "world"
+                    }
+                }
+            }
+        Notification.foo_bar(fqdn=["yahoo.com", "trafficserver.org"]) =>
+            {
+                "jsonrpc": "2.0",
+                "method": "foo_bar",
+                "params": {
+                    "fqdn": ["yahoo.com", "trafficserver.org"]
+                }
+            }
+
+    '''
+
+    def __init__(self, method: str, *args: typing.Any, **kwargs: typing.Any):
+        super(Notification, self).__init__(jsonrpc='2.0', method=method)
+        if args and kwargs:
+            plist = list(args)
+            plist.append(kwargs)
+            self.update(params=plist)
+        elif args:
+            if isinstance(args, tuple):
+                # fix this. this is to avoid having params=[[values] which seems invalid. Double check this as [[],[]] would be ok.
+                self.update(params=args[0])
+            else:
+                self.update(params=list(args))
+        elif kwargs:
+            self.update(params=kwargs)
+
+    # Allow using the dict item as an attribute.
+    def __getattr__(self, name):
+        if name in self:
+            return self[name]
+        else:
+            raise AttributeError("No such attribute: " + name)
+
+    def is_notification(self):
+        return True
+
+    def __str__(self) -> str:
+        return json.dumps(self)
+
+
+class Request(Notification):
+    '''
+    Convenient class to create JSONRPC objects and strings without the need to specify redundant information like
+    version or the id. All this will be generated automatically.
+
+    Examples:
+
+        Request.foo_bar() = > {"id": "e9ad55fe-d5a6-11eb-a2fd-fa163e6d2ec5", "jsonrpc": "2.0", "method": "foo_bar"}
+
+        Request.foo_bar({"hello"="world"}) =>
+            {
+                    "id": "850d2998-d5a7-11eb-bebc-fa163e6d2ec5",
+                    "jsonrpc": "2.0",
+                    "method": "foo_bar",
+                    "params": {
+                        "hello": "world"
+                    }
+                }
+
+        Request.foo_bar(var={"hello":"world"}) =>
+            {
+                "id": "850d2e84-d5a7-11eb-bebc-fa163e6d2ec5",
+                "jsonrpc": "2.0",
+                "method": "foo_bar",
+                "params": {
+                    "var": {
+                        "hello": "world"
+                    }
+                }
+            }
+        Request.foo_bar(fqdn=["yahoo.com", "trafficserver.org"]) =>
+            {
+                "id": "850d32a8-d5a7-11eb-bebc-fa163e6d2ec5",
+                "jsonrpc": "2.0",
+                "method": "foo_bar",
+                "params": {
+                    "fqdn": ["yahoo.com", "trafficserver.org"]
+                }
+            }
+
+    Note: Use full namespace to avoid name collision => jsonrpc.Request, jsonrpc.Response, etc.
+    '''
+
+    def __init__(self, method: str, *args: typing.Any, **kwargs: typing.Any):
+        if 'id' in kwargs:
+            self.update(id=kwargs.pop('id'))  # avoid duplicated
+        else:
+            self.update(id=str(uuid.uuid1()))
+
+        super(Request, self).__init__(method, *args, **kwargs)
+
+    def is_notification(self):
+        return False
+
+
+class BatchRequest(list):
+    def __init__(self, *args: typing.Union[Request, Notification]):
+        for r in args:
+            self.append(r)
+
+    def add_request(self, req: typing.Union[Request, Notification]):
+        self.append(req)
+
+    def __str__(self) -> str:
+        return json.dumps(self)
+
+
+class Response(dict):
+    '''
+    Convenient class to help handling jsonrpc responses. This can be source from text directly or by an already parsed test into
+    a json object.
+    '''
+
+    def __init__(self, *arg, **kwargs):
+        if 'text' in kwargs:
+            self.__dict__ = json.loads(kwargs['text'])
+        elif 'json' in kwargs:
+            self.__dict__ = kwargs['json']
+
+    def is_error(self) -> bool:
+        '''
+        Check whether the error field is present in the response. ie:
+            {
+                "jsonrpc":"2.0",
+                "error":{...},
+                "id":"284e0b86-d03a-11eb-9206-fa163e6d2ec5"
+            }
+        '''
+        if 'error' in self.__dict__:
+            return True
+        return False
+
+    def is_only_success(self) -> bool:
+        '''
+        Some responses may only have set the result as success. This functions checks that the value in the response field only
+        contains the 'success' string, ie:
+
+            {
+                "jsonrpc": "2.0",
+                "result": "success",
+                "id": "8504569c-d5a7-11eb-bebc-fa163e6d2ec5"
+            }
+        '''
+        if self.is_ok() and self.result == 'success':
+            return True
+
+        return False
+
+    def is_ok(self) -> bool:
+        '''
+        No error present in the response, the result field was properly set.
+        '''
+        return 'result' in self.__dict__
+
+    def error_as_str(self):
+        '''
+        Build up the error string.
+        {
+            "jsonrpc":"2.0",
+            "error":{
+                "code":9,
+                "message":"Error during execution",
+                "data":[
+                    {
+                        "code":10001,
+                        "message":"No values provided"
+                    }
+                ]
+            },
+            "id":"284e0b86-d03a-11eb-9206-fa163e6d2ec5"
+            }
+        '''
+        if self.is_ok():
+            return "no error"
+
+        errStr = ""
+        errStr += f"\n[code: {self.error['code']}, message: \"{self.error['message']}\"]"
+        if 'data' in self.error:
+            errStr += "\n\tAdditional Information:"
+            for err in self.error['data']:
+                errStr += f"\n\t - code: {err['code']}, message: \"{err['message']}\"."
+        return errStr
+
+    def __str__(self) -> str:
+        return json.dumps(self.__dict__)
+
+    def is_execution_error(self):
+        '''
+        Checks if the provided error is an Execution error(9).
+        '''
+        if self.is_ok():
+            return False
+
+        return self.error['code'] == 9
+
+    def contains_nested_error(self, code=None, msg=None):
+        if self.is_execution_error():
+            for err in self.error['data']:
+                if code and msg:
+                    return err['code'] == code and err['message'] == msg
+                elif code and err['code'] == code:
+                    return True
+                elif msg and err['message'] == msg:
+                    return True
+                else:
+                    return False
+        return False
+
+
+def make_response(text):
+    if text == '':
+        return None
+
+    s = json.loads(text)
+    if isinstance(s, dict):
+        return Response(json=s)
+    elif isinstance(s, list):
+        batch = []
+        for r in s:
+            batch.append(Response(json=r))
+        return batch
diff --git a/tests/gold_tests/autest-site/jsonrpc_client.test.ext b/tests/gold_tests/autest-site/jsonrpc_client.test.ext
new file mode 100644
index 0000000..974e981
--- /dev/null
+++ b/tests/gold_tests/autest-site/jsonrpc_client.test.ext
@@ -0,0 +1,295 @@
+'''
+JSONRPC client test extension.
+'''
+#  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.
+
+import os
+import tempfile
+import jsonrpc
+import json
+import sys
+import typing
+from jsonschema import validate
+from jsonschema.exceptions import ValidationError
+from autest.testers import Tester, tester
+import hosts.output as host
+from autest.exceptions.killonfailure import KillOnFailureError
+
+
+class SchemaValidator:
+    '''
+    Class that provides some handy schema validation function. It's in a class so some Testers can use this functionality without
+    exporting the class or method.
+    '''
+
+    def validate_request_schema(self, event, file_name, is_request=True, schema_file_name=None, field_schema_file_name=None):
+        '''
+        Perform Schema validation on the JSONRPC params and also to the particular params field.
+
+        file_name:
+            main json request file, schema check will be applied to the content of this file.
+        schema_file_name:
+            main doc schema file, this should only contain the wider JSONRPC 2.0 schema(not including params or result)
+        is_request:
+            This lets the function know if it should apply the 'field_schema_file_name' to the 'params' or the 'result' field.
+        field_schema_file_name:
+            param or result field schema file.
+
+        '''
+
+        with open(file_name, 'r') as f:
+            r = f.read()
+            rdata = json.loads(r)
+
+        if schema_file_name:
+            with open(schema_file_name, 'r') as f:
+                s = f.read()
+                sdata = json.loads(s)
+        try:
+            # validate may throw if invalid schema.
+            if schema_file_name:
+                validate(instance=rdata, schema=sdata)
+
+            if field_schema_file_name:
+                fieldName = 'params' if is_request else 'result'
+                jsonField = rdata[fieldName] if fieldName in rdata else None
+
+                if jsonField:
+                    with open(field_schema_file_name, 'r') as f:
+                        p = f.read()
+                        psdata = json.loads(p)
+                    validate(instance=jsonField, schema=psdata)
+                else:
+                    return (False, f"There is no {fieldName} field to validate", "Error found.")
+        except ValidationError as ve:
+            event.object.Stop()
+            return (False, "Check JSONRPC 2.0 schema validation", str(ve))
+
+        return (True, "Check JSONRPC 2.0 schema validation", "All good")
+
+
+def AddJsonRPCClientRequest(obj, ts, request='', file=None, schema_file_name=None, params_field_schema_file_name=None):
+    '''
+    Function to add a JSONRPC request into a process. This function will internally generate a call to traffic_ctl.
+    As traffic_ctl can send request by reading from a file, internally this function will create a temporary json file
+    and will be passed as parameter to traffic_ctl, taking only the output as response (-z).
+
+    Args:
+        ts:
+            traffic_server object, this is needed in order to traffic_ctl find the right socket.
+
+        file: The file name used to read the request.
+
+        request:
+            request should be created by the Request api(jsonrpc.py). ie:
+
+            tr = Test.AddTestRun("Test JSONRPC foo_bar()")
+            tr.AddJsonRPCClientRequest(ts, Request.foo_bar(fqdn=["yahoo.com", "aol.com", "vz.com"]))
+
+        schema_file_name:
+            Used to validate the request against a schema file. if empty no request schema
+            validation will be performed.
+
+        params_field_schema_file_name:
+            Schema file to validate the params field in the jsonrpc message.
+
+    Validating the response:
+
+    Either by the regular validation mechanism already provided by the Testing framework or by using CustomJSONRPCResponse Tester
+    which will let you read the response as a dict and play with it. See CustomJSONRPCResponse for more  details.
+
+    Errors:
+        If there is an error in the schema validation, either the params or the whole json message, the test will not run, an exception
+        will be thrown with the specific error.
+
+    '''
+
+    fileName = ''
+    process = obj.Processes.Default
+    if file is None:
+        reqFile = tempfile.NamedTemporaryFile(delete=False, dir=process.RunDirectory, suffix=f"_{obj.Name}.json")
+        fileName = reqFile.name
+        with open(fileName, "w") as req:
+            req.write(str(request))
+    else:
+        fileName = file
+
+    command = f"{ts.Variables.BINDIR}/traffic_ctl rpc file {fileName} "
+    if ts:
+        command += f" --run-root {ts.Disk.runroot_yaml.Name}"
+
+    command += ' --format json'  # we only want the output.
+
+    process.Command = command
+    process.ReturnCode = 0
+
+    if schema_file_name != "":
+        process.SetupEvent.Connect(
+            Testers.Lambda(
+                lambda ev: SchemaValidator().validate_request_schema(
+                    ev,
+                    fileName,
+                    True,
+                    schema_file_name,
+                    params_field_schema_file_name)))
+    return process
+
+
+def AddJsonRPCShowRegisterHandlerRequest(obj, ts):
+    '''
+    Handy function to request all the registered endpoints in the RPC engine. A good way to validate that your new RPC handler
+    is available through the RPC by calling this function and validating the response. ie:
+
+        tr = Test.AddTestRun("Test registered API - using AddJsonRPCShowRegisterHandlerRequest")
+        tr.AddJsonRPCShowRegisterHandlerRequest(ts)
+
+        tr.Processes.Default.Streams.stdout = All(
+            Testers.IncludesExpression('foo_bar', 'Should  be listed'),
+        )
+    '''
+    return AddJsonRPCClientRequest(obj, ts, jsonrpc.Request.show_registered_handlers())
+
+
+# Testers
+class CustomJSONRPCResponse(Tester):
+
+    '''
+    Custom tester that provides the user the ability to be called with the response from the RPC. The registered function will be
+    called with the jsonrpc.Response(jsonrpc.py).
+
+    Args:
+        func:
+            The function that will be called to perform a custom validation of the jsonrpc
+            message.
+
+    Example:
+
+        tr = Test.AddTestRun("Test update_host_status")
+        Params = [
+            {'name': 'yahoo', 'status': 'up'}
+        ]
+
+        tr.AddJsonRPCClientRequest(ts, Request.update_host_status(hosts=Params))
+
+
+        def check_no_error_on_response(resp: Response):
+            # we only check if it's an error.
+            if resp.is_error():
+                return (False, resp.error_as_str())
+            return (True, "All good")
+
+        tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(check_no_error_on_response)
+
+    '''
+
+    def __init__(self,
+                 func: typing.Any,
+                 test_value=None,
+                 kill_on_failure: bool = False,
+                 description_group: typing.Optional[str] = None,
+                 description: typing.Optional[str] = None):
+        if description is None:
+            description = "Validating JSONRPC 2.0 response"
+
+        super(CustomJSONRPCResponse, self).__init__(
+            value=func,
+            test_value=test_value,
+            kill_on_failure=kill_on_failure,
+            description_group=description_group,
+            description=description)
+
+    def test(self, eventinfo, **kw):
+
+        response_text = {}
+        with open(self._GetContent(eventinfo), "r") as resp:
+            response_text = resp.read()
+
+        (testPassed, reason) = self.Value(jsonrpc.Response(text=response_text))
+
+        if testPassed:
+            self.Result = tester.ResultType.Passed
+            self.Reason = f"Returned value: {reason}"
+            host.WriteVerbose(
+                ["testers.CustomJSONRPCResponse", "testers"], f"tester.ResultType.to_color_string(self.Result) - ", self.Reason)
+        else:
+            self.Result = tester.ResultType.Failed
+            self.Reason = f"Returned value: {reason}"
+            if self.KillOnFailure:
+                raise KillOnFailureError
+
+# Testers
+
+
+class JSONRPCResponseSchemaValidator(Tester, SchemaValidator):
+
+    '''
+    Tester for response schema validation.
+    This class can perform a JSONRPC 2.0 schema validation and also the 'result' field validation if provided.
+
+    schema_file_name:
+        Main JSONRPC 2.0 schema validation  file.
+
+    result_field_schema_file_name:
+        result field schema validation, this is optional, if not provided the main schema will just check that the result matches
+        the JSONRPC 2.0 specs.
+    '''
+
+    def __init__(self,
+                 schema_file_name,
+                 result_field_schema_file_name=None,
+                 value=None,
+                 test_value=None,
+                 kill_on_failure: bool = False,
+                 description_group: typing.Optional[str] = None,
+                 description: typing.Optional[str] = None):
+        if description is None:
+            description = "Validating JSONRPC 2.0 response schema"
+        self._schema_file_name = schema_file_name
+        self._result_field_schema_file_name = result_field_schema_file_name
+
+        super(JSONRPCResponseSchemaValidator, self).__init__(
+            value=value,
+            test_value=test_value,
+            kill_on_failure=kill_on_failure,
+            description_group=description_group,
+            description=description)
+
+    def test(self, eventinfo, **kw):
+        response_text = {}
+        with open(self._GetContent(eventinfo), "r") as resp:
+            response_text = resp.read()
+
+        (testPassed, reason, cmm) = self.validate_request_schema(eventinfo, self._GetContent(
+            eventinfo), False, self._schema_file_name, self._result_field_schema_file_name)
+
+        if testPassed:
+            self.Result = tester.ResultType.Passed
+            self.Reason = f"Returned value: {reason}"
+            host.WriteVerbose(["testers.JSONRPCResponseSchemaValidator", "testers"],
+                              f"tester.ResultType.to_color_string(self.Result) - ", self.Reason)
+        else:
+            self.Result = tester.ResultType.Failed
+            self.Reason = f"Returned value: {reason}"
+            if self.KillOnFailure:
+                raise KillOnFailureError
+
+
+# Export
+AddTester(CustomJSONRPCResponse)
+AddTester(JSONRPCResponseSchemaValidator)
+ExtendTestRun(AddJsonRPCShowRegisterHandlerRequest, name="AddJsonRPCShowRegisterHandlerRequest")
+ExtendTestRun(AddJsonRPCClientRequest, name="AddJsonRPCClientRequest")
diff --git a/tests/gold_tests/autest-site/trafficserver.test.ext b/tests/gold_tests/autest-site/trafficserver.test.ext
index 1fe9eed..89d5cfa 100755
--- a/tests/gold_tests/autest-site/trafficserver.test.ext
+++ b/tests/gold_tests/autest-site/trafficserver.test.ext
@@ -41,7 +41,8 @@
 def MakeATSProcess(obj, name, command='traffic_server', select_ports=True,
                    enable_tls=False, enable_cache=True, enable_quic=False,
                    block_for_debug=False, log_data=default_log_data,
-                   use_traffic_out=True, enable_proxy_protocol=False):
+                   use_traffic_out=True, dump_runroot=True,
+                   enable_proxy_protocol=False):
     #####################################
     # common locations
 
@@ -84,19 +85,11 @@
     if block_for_debug:
         ts_args += ' --block'
 
-    if 'traffic_manager' in command:
-        if '--tsArgs' in command:
-            # None of our tests do this, but if we eventually pass in --tsArgs
-            # at some point from a test, we'll need to figure out how to mix
-            # that with the arguments we're assembling for --tsArgs here.
-            host.WriteError('Do not know how to assemble --tsArgs.')
-        if ts_args:
-            command += f' --tsArgs "{ts_args}"'
-    elif 'traffic_server' in command:
-        command += ts_args
-
+    # Have the full command including arguments in the cmdline so tool scripts
+    # can eventually use some keyword from the path to trace a particular TS instance
+    process_cmd = f"{os.path.join(ts_dir, bin_dir)}/{command} {ts_args}"
     # create process
-    p = obj.Processes.Process(name, command)
+    p = obj.Processes.Process(name, process_cmd)
     #p_debug = obj.Processes.Process("port-debug", "ss --listen --tcp --process")
     #p_debug.Env['PATH'] = "/usr/sbin" + os.pathsep + p.ComposeEnv()['PATH']
     # p.StartBefore(p_debug)
@@ -177,9 +170,6 @@
     p.Setup.MakeDir(runtime_dir)
     p.chownForATSProcess(runtime_dir)
 
-    # will need this for traffic_manager is it runs
-    p.Setup.MakeDir(os.path.join(config_dir, 'snapshots'))
-    p.Env['PROXY_CONFIG_SNAPSHOT_DIR'] = os.path.join(config_dir, 'snapshots')
     ##########################################################
     # create subdirectories that need to exist (but are empty)
     # ssl directory has to be created for keeping certs and keys
@@ -241,16 +231,6 @@
     tmpname = os.path.join(log_dir, fname)
     p.Disk.File(tmpname, id='traffic_out')
 
-    if "traffic_manager" in command:
-        fname = log_data['manager']
-        if fname == 'stdout':
-            p.Disk.manager_log = p.Streams.stdout
-        elif fname == 'stderr':
-            p.Disk.manager_log = p.Streams.stderr
-        else:
-            tmpname = os.path.join(log_dir, fname)
-            p.Disk.File(tmpname, id='manager_log')
-
     # config files
     def MakeConfigFile(self, fname):
         tmpname = os.path.join(config_dir, fname)
@@ -315,6 +295,26 @@
     tmpname = os.path.join(config_dir, fname)
     p.Disk.File(tmpname, id=make_id(fname), typename="ats:config")
 
+    # The big motivation in exposing this file is that we need to tell the traffic_ctl
+    # where to find the socket to interact with the TS. traffic_ctl cannot rely only
+    # in the build layout for unit test.
+    fname = "runroot.yaml"
+    tmpname = os.path.join(config_dir, fname)
+    p.Disk.File(tmpname, id=make_id(fname), typename="ats:config")
+
+    # Fill in the runroot file and set the config var, this will be used traffic_server
+    # and traffic_ctl
+    if dump_runroot:
+        p.Disk.runroot_yaml.AddLines([f'runtimedir: {runtime_dir}',
+                                      f'cachedir: {cache_dir}',
+                                      f'localstatedir: {runtime_dir}',
+                                      f'bindir: {bin_dir}',
+                                      f'prefix: {ts_dir}',
+                                      f'logdir: {log_dir}',
+                                      f'sysconfdir: {config_dir}'])
+        # Add more if needed
+        p.Env['TS_RUNROOT'] = os.path.join(config_dir, "runroot.yaml")
+
     ##########################################################
     # set up default ports
     # get some ports  TODO make it so we can hold on to the socket
@@ -405,10 +405,7 @@
     p.Env['PROXY_CONFIG_ADMIN_AUTOCONF_PORT'] = str(
         p.Variables.admin_port)  # support pre ATS 6.x
 
-    if 'traffic_manager' in command:
-        p.ReturnCode = 2
-    else:
-        p.ReturnCode = 0
+    p.ReturnCode = 0
 
     if block_for_debug:
         p.Env['PROXY_BLOCK'] = '1'
diff --git a/tests/gold_tests/cache/cache-control.test.py b/tests/gold_tests/cache/cache-control.test.py
index 6d49140..6d43530 100644
--- a/tests/gold_tests/cache/cache-control.test.py
+++ b/tests/gold_tests/cache/cache-control.test.py
@@ -45,7 +45,7 @@
 server.addResponse("sessionlog.json", request_header3, response_header3)
 
 # ATS Configuration
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via')
 ts.Disk.records_config.update({
     'proxy.config.diags.debug.enabled': 1,
     'proxy.config.diags.debug.tags': 'http',
diff --git a/tests/gold_tests/cache/cache-generation-clear.test.py b/tests/gold_tests/cache/cache-generation-clear.test.py
index e066aee..72d23ac 100644
--- a/tests/gold_tests/cache/cache-generation-clear.test.py
+++ b/tests/gold_tests/cache/cache-generation-clear.test.py
@@ -23,7 +23,7 @@
 '''
 Test.ContinueOnFail = True
 # Define default ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 # setup some config file for this server
 ts.Disk.records_config.update({
@@ -31,7 +31,8 @@
     'proxy.config.http.cache.generation': -1,  # Start with cache turned off
     'proxy.config.config_update_interval_ms': 1,
 })
-ts.Disk.plugin_config.AddLine('xdebug.so')
+
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via,x-cache-generation')
 ts.Disk.remap_config.AddLines([
     'map /default/ http://127.0.0.1/ @plugin=generator.so',
     # line 2
@@ -62,7 +63,7 @@
 
 # Call traffic_ctrl to set new generation
 tr = Test.AddTestRun()
-tr.Processes.Default.Command = 'traffic_ctl --debug config set proxy.config.http.cache.generation 77'
+tr.Processes.Default.Command = f'traffic_ctl --debug config set proxy.config.http.cache.generation 77'
 tr.Processes.Default.ForceUseShell = False
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Env = ts.Env  # set the environment for traffic_control to run in
diff --git a/tests/gold_tests/cache/cache-generation-disjoint.test.py b/tests/gold_tests/cache/cache-generation-disjoint.test.py
index d2d0491..5f3988d 100644
--- a/tests/gold_tests/cache/cache-generation-disjoint.test.py
+++ b/tests/gold_tests/cache/cache-generation-disjoint.test.py
@@ -33,7 +33,7 @@
     'proxy.config.config_update_interval_ms': 1,
 
 })
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via,x-cache-generation')
 ts.Disk.remap_config.AddLines([
     'map /default/ http://127.0.0.1/ @plugin=generator.so',
     # line 2
diff --git a/tests/gold_tests/cache/disjoint-wait-for-cache.test.py b/tests/gold_tests/cache/disjoint-wait-for-cache.test.py
index 2e9669f..9c59968 100644
--- a/tests/gold_tests/cache/disjoint-wait-for-cache.test.py
+++ b/tests/gold_tests/cache/disjoint-wait-for-cache.test.py
@@ -35,7 +35,7 @@
     'proxy.config.config_update_interval_ms': 1,
     'proxy.config.http.wait_for_cache': 3,
 })
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via,x-cache-generation')
 ts.Disk.remap_config.AddLines([
     'map /default/ http://127.0.0.1/ @plugin=generator.so',
     # line 2
diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
index d52a68b..dee7b7f 100644
--- a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
+++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
@@ -25,7 +25,7 @@
 Test.ContinueOnFail = True
 
 # Define default ATS
-ts = Test.MakeATSProcess("ts1", enable_tls=False)
+ts = Test.MakeATSProcess("ts1")
 server = Test.MakeOriginServer("server")
 
 testName = ""
diff --git a/tests/gold_tests/cont_schedule/gold/schedule.gold b/tests/gold_tests/cont_schedule/gold/schedule.gold
deleted file mode 100644
index 7b5d1f6..0000000
--- a/tests/gold_tests/cont_schedule/gold/schedule.gold
+++ /dev/null
@@ -1,4 +0,0 @@
-``
-``(TSContSchedule_test.check) pass [should be the same thread]
-``(TSContSchedule_test.check) pass [should not be the same thread]
-``
diff --git a/tests/gold_tests/cont_schedule/schedule.test.py b/tests/gold_tests/cont_schedule/schedule.test.py
deleted file mode 100644
index 53a2599..0000000
--- a/tests/gold_tests/cont_schedule/schedule.test.py
+++ /dev/null
@@ -1,49 +0,0 @@
-'''
-'''
-#  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.
-
-
-import os
-
-Test.Summary = 'Test TSContSchedule API'
-Test.ContinueOnFail = True
-
-# Define default ATS
-ts = Test.MakeATSProcess('ts')
-
-Test.testName = 'Test TSContSchedule API'
-
-ts.Disk.records_config.update({
-    'proxy.config.exec_thread.autoconfig': 0,
-    'proxy.config.exec_thread.autoconfig.scale': 1.5,
-    'proxy.config.exec_thread.limit': 32,
-    'proxy.config.accept_threads': 1,
-    'proxy.config.task_threads': 2,
-    'proxy.config.diags.debug.enabled': 1,
-    'proxy.config.diags.debug.tags': 'TSContSchedule_test'
-})
-
-# Load plugin
-Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'cont_schedule.so'), ts, 'thread')
-
-# www.example.com Host
-tr = Test.AddTestRun()
-tr.Processes.Default.Command = 'printf "Test TSContSchedule API"'
-tr.Processes.Default.ReturnCode = 0
-tr.Processes.Default.StartBefore(ts)
-ts.Disk.traffic_out.Content = "gold/schedule.gold"
-ts.Disk.traffic_out.Content += Testers.ExcludesExpression('fail', 'should not contain "fail"')
diff --git a/tests/gold_tests/continuations/double.test.py b/tests/gold_tests/continuations/double.test.py
index f150dc9..52b11ca 100644
--- a/tests/gold_tests/continuations/double.test.py
+++ b/tests/gold_tests/continuations/double.test.py
@@ -24,7 +24,7 @@
 
 Test.ContinueOnFail = True
 # Define default ATS. Disable the cache to simplify the test.
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 server = Test.MakeOriginServer("server")
 
 Test.testName = ""
diff --git a/tests/gold_tests/continuations/double_h2.test.py b/tests/gold_tests/continuations/double_h2.test.py
index 179e22c..541e09d 100644
--- a/tests/gold_tests/continuations/double_h2.test.py
+++ b/tests/gold_tests/continuations/double_h2.test.py
@@ -26,10 +26,11 @@
 )
 Test.ContinueOnFail = True
 # Define default ATS. Disable the cache to simplify the test.
-ts = Test.MakeATSProcess("ts", enable_tls=True, command="traffic_manager", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server2")
 
+
 Test.testName = ""
 request_header = {"headers": "GET / HTTP/1.1\r\nHost: double_h2.test\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
 # expected response from the origin server
diff --git a/tests/gold_tests/continuations/openclose.test.py b/tests/gold_tests/continuations/openclose.test.py
index 178137a..4478607 100644
--- a/tests/gold_tests/continuations/openclose.test.py
+++ b/tests/gold_tests/continuations/openclose.test.py
@@ -23,7 +23,7 @@
 '''
 
 # Define default ATS. Disable the cache to simplify the test.
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server2")
@@ -71,6 +71,7 @@
 server.StartAfter(*ps)
 tr.StillRunningAfter = ts
 
+
 # Signal that all the curl processes have completed
 tr = Test.AddTestRun("Curl Done")
 tr.DelayStart = 2  # Delaying a couple seconds to make sure the global continuation's lock contention resolves.
diff --git a/tests/gold_tests/continuations/openclose_h2.test.py b/tests/gold_tests/continuations/openclose_h2.test.py
index 8f8e99d..bc146dc 100644
--- a/tests/gold_tests/continuations/openclose_h2.test.py
+++ b/tests/gold_tests/continuations/openclose_h2.test.py
@@ -27,7 +27,8 @@
 )
 
 # Define default ATS. Disable the cache to simplify the test.
-ts = Test.MakeATSProcess("ts", enable_tls=True, command="traffic_manager", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
+
 
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server2")
diff --git a/tests/gold_tests/continuations/session_id.test.py b/tests/gold_tests/continuations/session_id.test.py
index 2114898..3c0037e 100644
--- a/tests/gold_tests/continuations/session_id.test.py
+++ b/tests/gold_tests/continuations/session_id.test.py
@@ -34,7 +34,7 @@
 server.addResponse("sessionfile.log", request_header, response_header)
 
 # Configure ATS. Disable the cache to simplify the test.
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True, enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
 
 ts.addDefaultSSLFiles()
 
diff --git a/tests/gold_tests/h2/http2_flow_control.test.py b/tests/gold_tests/h2/http2_flow_control.test.py
index babb1a4..0094583 100644
--- a/tests/gold_tests/h2/http2_flow_control.test.py
+++ b/tests/gold_tests/h2/http2_flow_control.test.py
@@ -28,10 +28,13 @@
     """Define an object to test HTTP/2 flow control behavior."""
 
     _replay_file: str = 'http2_flow_control.replay.yaml'
-    _valid_policy_values: List[int] = list(range(0, 2))
+    _valid_policy_values: List[int] = list(range(0, 3))
+    _flow_control_policy: Optional[int] = None
+    _flow_control_policy_is_malformed: bool = False
 
     _default_initial_window_size: int = 65535
     _default_max_concurrent_streams_in: int = 100
+    _default_flow_control_policy: int = 0
 
     _dns_counter: int = 0
     _server_counter: int = 0
@@ -43,7 +46,7 @@
             description: str,
             initial_window_size: Optional[int] = None,
             max_concurrent_streams_in: Optional[int] = None,
-            verify_window_update_frames: bool = False):
+            flow_control_policy: Optional[int] = None):
         """Declare the various test Processes.
 
         :param description: A description of the test.
@@ -58,13 +61,16 @@
         records.config file. If the paramenter is None, then no window size
         will be explicitly set and ATS will use the default value.
 
-        :param verify_window_update_frames: If True, then the test will verify
-        that it sees HTTP/2 WINDOW_UPDATE frames as data is sent.
+        :param flow_control_policy: The value with which to configure the
+        proxy.config.http2.flow_control.policy_in ATS parameter the
+        records.config file. If the paramenter is None, then no policy
+        configuration will be explicitly set and ATS will use the default
+        value.
         """
         self._description = description
 
         self._initial_window_size = initial_window_size
-        self._expected_initial_window_size = (
+        self._expected_initial_stream_window_size = (
             initial_window_size if initial_window_size is not None
             else self._default_initial_window_size)
 
@@ -73,7 +79,14 @@
             max_concurrent_streams_in if max_concurrent_streams_in is not None
             else self._default_max_concurrent_streams_in)
 
-        self._verify_window_update_frames = verify_window_update_frames
+        self._flow_control_policy = flow_control_policy
+        self._expected_flow_control_policy = (
+            flow_control_policy if flow_control_policy is not None
+            else self._default_flow_control_policy)
+
+        self._flow_control_policy_is_malformed = (
+            self._flow_control_policy is not None and
+            self._flow_control_policy not in self._valid_policy_values)
 
         self._dns = self._configure_dns()
         self._server = self._configure_server()
@@ -117,6 +130,11 @@
                 'proxy.config.http2.initial_window_size_in': self._initial_window_size,
             })
 
+        if self._flow_control_policy is not None:
+            ts.Disk.records_config.update({
+                'proxy.config.http2.flow_control.policy_in': self._flow_control_policy,
+            })
+
         if self._max_concurrent_streams_in is not None:
             ts.Disk.records_config.update({
                 'proxy.config.http2.max_concurrent_streams_in': self._max_concurrent_streams_in,
@@ -130,6 +148,11 @@
             f'map / http://127.0.0.1:{self._server.Variables.http_port}'
         )
 
+        if self._flow_control_policy_is_malformed:
+            ts.Disk.diags_log.Content = Testers.ContainsExpression(
+                "ERROR.*proxy.config.http2.flow_control.policy_in",
+                "There should be an about an invalid flow control policy.")
+
         return ts
 
     def _configure_client(self, tr):
@@ -143,39 +166,96 @@
             https_ports=[self._ts.Variables.ssl_port])
         Http2FlowControlTest._client_counter += 1
 
+        if self._flow_control_policy_is_malformed:
+            # Since we're just testing ATS configuration errors, there's no
+            # need to set up client expectations.
+            return
+
+        # ATS currently always sends a MAX_CONCURRENT_STREAMS setting.
         tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
             f'MAX_CONCURRENT_STREAMS:{self._expected_max_concurrent_streams_in}',
             "Client should receive a MAX_CONCURRENT_STREAMS setting.")
 
         if self._initial_window_size is not None:
             tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-                f'INITIAL_WINDOW_SIZE:{self._expected_initial_window_size}',
+                f'INITIAL_WINDOW_SIZE:{self._expected_initial_stream_window_size}',
                 "Client should receive an INITIAL_WINDOW_SIZE setting.")
 
-        if self._verify_window_update_frames:
-            tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-                f'WINDOW_UPDATE.*id 0: {self._expected_initial_window_size}',
-                "Client should receive a session WINDOW_UPDATE.")
+        if self._expected_flow_control_policy == 0:
+            update_window_size = (
+                self._expected_initial_stream_window_size -
+                self._default_initial_window_size)
+            if update_window_size > 0:
+                tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
+                    f'WINDOW_UPDATE.*id 0: {update_window_size}',
+                    "Client should receive a session WINDOW_UPDATE.")
 
+        if self._expected_flow_control_policy in (1, 2):
+            # Verify the larger window size.
+
+            session_window_size = (
+                self._expected_initial_stream_window_size *
+                self._expected_max_concurrent_streams_in)
+
+            # ATS will send a WINDOW_UPDATE frame to the client to increase
+            # the session window size to the configured value from the default
+            # value.
+            update_window_size = (
+                session_window_size - self._expected_initial_stream_window_size)
+
+            # A WINDOW_UPDATE can only increase the window size. So make sure that
+            # the new window size is greater than the default window size.
+            if update_window_size > Http2FlowControlTest._default_initial_window_size:
+                tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
+                    f'WINDOW_UPDATE.*id 0: {update_window_size}',
+                    "Client should receive an initial session WINDOW_UPDATE.")
+            else:
+                # Our test traffic is large enough that eventually we should
+                # send a session WINDOW_UPDATE frame for the smaller window.
+                # It's not clear what it will be in advance though. A 100 byte
+                # session window may not receive a 100 byte WINDOW_UPDATE frame
+                # if the client is sending DATA frames in 10 byte chunks due to
+                # a smaller stream window.
+                tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
+                    'WINDOW_UPDATE.*id 0: ',
+                    "Client should receive a session WINDOW_UPDATE.")
+
+            if self._expected_flow_control_policy == 2:
+                # Verify the streams window sizes get updated.
+                stream_window_1 = session_window_size
+                stream_window_2 = int(session_window_size / 2)
+                stream_window_3 = int(session_window_size / 3)
+                tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
+                    (f'INITIAL_WINDOW_SIZE:{stream_window_1}.*'
+                     f'INITIAL_WINDOW_SIZE:{stream_window_2}.*'
+                     f'INITIAL_WINDOW_SIZE:{stream_window_3}'),
+                    "Client should stream receive window updates",
+                    reflags=re.DOTALL | re.MULTILINE)
+
+        if self._expected_initial_stream_window_size < 1000:
+            # For the smaller session window sizes, we expect WINDOW_UPDATE frames.
             tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-                f'WINDOW_UPDATE.*id 3: {self._expected_initial_window_size}',
+                f'WINDOW_UPDATE.*id 3: {self._expected_initial_stream_window_size}',
                 "Client should receive a stream WINDOW_UPDATE.")
 
             tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-                f'WINDOW_UPDATE.*id 5: {self._expected_initial_window_size}',
+                f'WINDOW_UPDATE.*id 5: {self._expected_initial_stream_window_size}',
                 "Client should receive a stream WINDOW_UPDATE.")
 
             tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-                f'WINDOW_UPDATE.*id 7: {self._expected_initial_window_size}',
+                f'WINDOW_UPDATE.*id 7: {self._expected_initial_stream_window_size}',
                 "Client should receive a stream WINDOW_UPDATE.")
 
     def run(self):
         """Configure the TestRun."""
         tr = Test.AddTestRun(self._description)
-        self._configure_client(tr)
-        tr.Processes.Default.StartBefore(self._dns)
-        tr.Processes.Default.StartBefore(self._server)
-        tr.StillRunningAfter = self._ts
+        if not self._flow_control_policy_is_malformed:
+            self._configure_client(tr)
+            tr.Processes.Default.StartBefore(self._dns)
+            tr.Processes.Default.StartBefore(self._server)
+            tr.StillRunningAfter = self._ts
+        else:
+            tr.Processes.Default.Command = "true"
         tr.Processes.Default.StartBefore(self._ts)
 
 
@@ -197,14 +277,31 @@
 # Configuring initial_window_size.
 #
 test = Http2FlowControlTest(
-    description="Configure a large initial_window_size_in",
+    description="Configure a larger initial_window_size_in",
     initial_window_size=100123)
 test.run()
 
-
+#
+# Configuring flow_control_policy.
+#
 test = Http2FlowControlTest(
-    description="Configure a small initial_window_size_in",
+    description="Configure an unrecognized flow_control.in.policy",
+    flow_control_policy=23)
+test.run()
+test = Http2FlowControlTest(
+    description="Flow control policy 0 (default): small initial_window_size_in",
+    initial_window_size=500,  # The default is 65 KB.
+    flow_control_policy=0)
+test.run()
+test = Http2FlowControlTest(
+    description="Flow control policy 1: 100 byte session, 10 byte stream windows",
     max_concurrent_streams_in=10,
     initial_window_size=10,
-    verify_window_update_frames=True)
+    flow_control_policy=1)
+test.run()
+test = Http2FlowControlTest(
+    description="Flow control policy 2: 100 byte session, dynamic stream windows",
+    max_concurrent_streams_in=10,
+    initial_window_size=10,
+    flow_control_policy=2)
 test.run()
diff --git a/tests/gold_tests/headers/cache_and_req_body.test.py b/tests/gold_tests/headers/cache_and_req_body.test.py
index b2ab6d0..dfdda32 100644
--- a/tests/gold_tests/headers/cache_and_req_body.test.py
+++ b/tests/gold_tests/headers/cache_and_req_body.test.py
@@ -37,7 +37,7 @@
 server.addResponse("sessionlog.json", request_header, response_header)
 
 # ATS Configuration
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via')
 ts.Disk.records_config.update({
     'proxy.config.diags.debug.enabled': 1,
     'proxy.config.diags.debug.tags': 'http',
diff --git a/tests/gold_tests/headers/cachedIMSRange.test.py b/tests/gold_tests/headers/cachedIMSRange.test.py
index 074a751..d1f3e10 100644
--- a/tests/gold_tests/headers/cachedIMSRange.test.py
+++ b/tests/gold_tests/headers/cachedIMSRange.test.py
@@ -82,7 +82,7 @@
 
 # ATS Configuration
 ts = Test.MakeATSProcess("ts", enable_tls=True)
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via')
 ts.addDefaultSSLFiles()
 ts.Disk.ssl_multicert_config.AddLine(
     'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
diff --git a/tests/gold_tests/headers/forwarded.test.py b/tests/gold_tests/headers/forwarded.test.py
index 5478c5f..3c8aab8 100644
--- a/tests/gold_tests/headers/forwarded.test.py
+++ b/tests/gold_tests/headers/forwarded.test.py
@@ -192,11 +192,12 @@
 TestHttp1_1('www.forwarded-connection-std.com')
 TestHttp1_1('www.forwarded-connection-full.com')
 
-ts2 = Test.MakeATSProcess("ts2", command="traffic_manager", enable_tls=True)
+ts2 = Test.MakeATSProcess("ts2", enable_tls=True)
 
 baselineTsSetup(ts2)
 
 ts2.Disk.records_config.update({
+    # 'proxy.config.diags.debug.enabled': 1,
     'proxy.config.url_remap.pristine_host_hdr': 1,  # Retain Host header in original incoming client request.
     'proxy.config.http.insert_forwarded': 'by=uuid'})
 
@@ -204,6 +205,7 @@
     'map https://www.no-oride.com http://127.0.0.1:{0}'.format(server.Variables.Port)
 )
 
+
 # Forwarded header with UUID of 2nd ATS.
 tr = Test.AddTestRun()
 # Delay on readiness of our ssl ports
diff --git a/tests/gold_tests/ip_allow/ip_allow.test.py b/tests/gold_tests/ip_allow/ip_allow.test.py
index c1ac984..5ec69d5 100644
--- a/tests/gold_tests/ip_allow/ip_allow.test.py
+++ b/tests/gold_tests/ip_allow/ip_allow.test.py
@@ -24,8 +24,7 @@
 Test.ContinueOnFail = True
 
 # Define default ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager",
-                         enable_tls=True, enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
 server = Test.MakeOriginServer("server", ssl=True)
 
 testName = ""
@@ -95,11 +94,11 @@
 })
 
 format_string = ('%<cqtd>-%<cqtt> %<stms> %<ttms> %<chi> %<crc>/%<pssc> %<psql> '
-                 '%<cqhm> %<cquc> %<phr>/%<pqsn> %<psct> %<{Y-RID}pqh> '
+                 '%<cqhm> %<pquc> %<phr>/%<pqsn> %<psct> %<{Y-RID}pqh> '
                  '%<{Y-YPCS}pqh> %<{Host}cqh> %<{CHAD}pqh>  '
                  'sftover=%<{x-safet-overlimit-rules}cqh> sftmat=%<{x-safet-matched-rules}cqh> '
                  'sftcls=%<{x-safet-classification}cqh> '
-                 'sftbadclf=%<{x-safet-bad-classifiers}cqh> yra=%<{Y-RA}cqh> scheme=%<cqus>')
+                 'sftbadclf=%<{x-safet-bad-classifiers}cqh> yra=%<{Y-RA}cqh> scheme=%<pqus>')
 
 ts.Disk.logging_yaml.AddLines(
     ''' logging:
diff --git a/tests/gold_tests/jsonrpc/basic_plugin_handler.test.py b/tests/gold_tests/jsonrpc/basic_plugin_handler.test.py
new file mode 100644
index 0000000..073a988
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/basic_plugin_handler.test.py
@@ -0,0 +1,182 @@
+'''
+'''
+#  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.
+
+
+import os
+import sys
+from jsonrpc import Notification, Request, Response
+
+Test.Summary = 'Test JSONRPC Handler inside a plugin'
+Test.ContinueOnFail = True
+# Define default ATS, we will use the runroot file for traffic_ctl
+ts = Test.MakeATSProcess('ts', dump_runroot=True)
+
+Test.testName = 'Basic plugin handler test'
+
+ts.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'rpc|jsonrpc_plugin_handler_test'
+})
+
+# Load plugin
+Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsBuildGoldTestsDir,
+                                    'jsonrpc', 'plugins', '.libs', 'jsonrpc_plugin_handler_test.so'), ts)
+
+tr = Test.AddTestRun()
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Command = "echo run jsonrpc_plugin_handler_test plugin"
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.StillRunningAfter = ts
+ts.Ready = 0
+ts.Disk.traffic_out.Content = All(
+    Testers.IncludesExpression(
+        'Test Plugin Initialized.',
+        'plugin should be properly initialized'),
+    Testers.IncludesExpression(
+        'test_join_hosts_method successfully registered',
+        'test_join_hosts_method should be properly registered'),
+    Testers.IncludesExpression(
+        'test_join_hosts_notification successfully registered',
+        'test_join_hosts_notification should be properly registered'))
+
+
+# 1 - now let's try the registered api.
+tr = Test.AddTestRun("Test registered API")
+tr.AddJsonRPCClientRequest(ts, Request.show_registered_handlers())
+tr.DelayStart = 2
+# Ok we can just check like this:
+tr.Processes.Default.Streams.stdout = All(
+    Testers.IncludesExpression('test_join_hosts_method', 'Should  be listed'),
+    Testers.IncludesExpression('test_join_hosts_notification', 'Should be listed')
+)
+
+# 2 - We perform the same as above but without implicit 'show_registered_handlers()' call.
+tr = Test.AddTestRun("Test registered API - using AddJsonRPCShowRegisterHandlerRequest")
+tr.AddJsonRPCShowRegisterHandlerRequest(ts)
+
+# Ok we can just check like this:
+tr.Processes.Default.Streams.stdout = All(
+    Testers.IncludesExpression('test_join_hosts_method', 'Should  be listed'),
+    Testers.IncludesExpression('test_join_hosts_notification', 'Should be listed')
+)
+
+# 3 - Call the actual plugin method handler:
+tr = Test.AddTestRun("Test JSONRPC test_join_hosts_method")
+tr.AddJsonRPCClientRequest(ts, Request.test_join_hosts_method(hosts=["yahoo.com", "aol.com", "vz.com"]))
+
+
+def validate_host_response(resp: Response):
+    '''
+    Custom check for a particular response values. Also error validation.
+    '''
+    if resp.is_error():
+        return (False, resp.error_as_str())
+
+    join_str = resp.result['join']
+    expected = "yahoo.comaol.comvz.com"
+    if join_str != expected:
+        return (False, f"Invalid response, expected yahoo.comaol.comvz.com, got {join_str}")
+
+    return (True, "All good")
+
+
+tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_host_response)
+
+# Custom response validator:
+
+
+def _validate_response_for_test_io_on_et_task(added, updated, resp: Response):
+    '''
+    Custom check for a particular response values. Also error validation.
+    '''
+    if resp.is_error():
+        return (False, resp.error_as_str())
+
+    addedHosts = resp.result['addedHosts']
+    updatedHosts = resp.result['updatedHosts']
+
+    if addedHosts == added and updatedHosts == updated:
+        return (True, "All good")
+
+    return (
+        False,
+        f"Invalid response, expected addedHosts == {added} and updatedHosts == {updated}, got {addedHosts} and {updatedHosts}")
+
+
+# 4 - Call plugin handler to perform a IO task on the ET_TASK thread.
+tr = Test.AddTestRun("Test test_io_on_et_task")
+newHosts = [
+    {'name': 'brbzull', 'status': 'up'},
+    {'name': 'brbzull1', 'status': 'down'},
+    {'name': 'brbzull3', 'status': 'up'},
+    {'name': 'brbzull4', 'status': 'down'},
+    {'name': 'yahoo', 'status': 'down'},
+    {'name': 'trafficserver', 'status': 'down'}
+]
+
+# the jsonrpc query
+tr.AddJsonRPCClientRequest(ts, Request.test_io_on_et_task(hosts=newHosts))
+
+
+def validate_response_for_test_io_on_et_task_1(resp: Response):
+    return _validate_response_for_test_io_on_et_task('6', '0', resp)
+
+
+tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_response_for_test_io_on_et_task_1)
+
+# 5
+tr = Test.AddTestRun("Test test_io_on_et_task - update, yahoo up")
+updateYahoo = [
+    {'name': 'yahoo', 'status': 'up'}
+]
+
+# the jsonrpc query
+tr.AddJsonRPCClientRequest(ts, Request.test_io_on_et_task(hosts=updateYahoo))
+
+
+def validate_response_for_test_io_on_et_task_2(resp: Response):
+    return _validate_response_for_test_io_on_et_task('0', '1', resp)
+
+
+tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_response_for_test_io_on_et_task_2)
+
+# 6
+tr = Test.AddTestRun("Test privileged field from plugin handlers")
+tr.AddJsonRPCClientRequest(ts, Request.get_service_descriptor())
+
+
+def validate_privileged_field_for_method(resp: Response, params):
+    for (name, val) in params:
+        if resp.is_error():
+            return (False, resp.error_as_str())
+
+        data = resp.result['methods']
+        e = list(filter(lambda x: x['name'] == name, data))[0]
+        # Method should be registered.
+        if e is None or e['privileged'] != val:
+            return (False, f"{name} privileged != {val}")
+
+    return (True, "All good")
+
+
+def validate_privileged_field(resp: Response):
+    return validate_privileged_field_for_method(resp, [('test_join_hosts_method', "1"), ('test_io_on_et_task', '1'),
+                                                       ('test_join_hosts_notification', "0")])
+
+
+tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_privileged_field)
diff --git a/tests/gold_tests/jsonrpc/json/admin_clear_metrics_records_req.json b/tests/gold_tests/jsonrpc/json/admin_clear_metrics_records_req.json
new file mode 100644
index 0000000..24881bf
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_clear_metrics_records_req.json
@@ -0,0 +1,10 @@
+{
+   "id":"0ea2672f-b369-4d99-8d46-98c3fadc152d",
+   "jsonrpc":"2.0",
+   "method":"admin_clear_metrics_records",
+   "params":[
+      {
+         "record_name":"$record_name"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_config_reload_req.json b/tests/gold_tests/jsonrpc/json/admin_config_reload_req.json
new file mode 100644
index 0000000..19fda47
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_config_reload_req.json
@@ -0,0 +1,5 @@
+{
+   "id":"71588e95-4f11-43a9-9c7d-9942e017548c",
+   "jsonrpc":"2.0",
+   "method":"admin_config_reload"
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_config_set_records_req.json b/tests/gold_tests/jsonrpc/json/admin_config_set_records_req.json
new file mode 100644
index 0000000..00df611
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_config_set_records_req.json
@@ -0,0 +1,11 @@
+{
+   "id": "a32de1da-08be-11eb-9e1e-001fc69cc946",
+   "jsonrpc": "2.0",
+   "method": "admin_config_set_records",
+   "params": [
+      {
+            "record_name": "$record_name",
+            "record_value": "$record_value"
+      }
+   ]
+}
diff --git a/tests/gold_tests/jsonrpc/json/admin_host_set_status_req.json b/tests/gold_tests/jsonrpc/json/admin_host_set_status_req.json
new file mode 100644
index 0000000..8079159
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_host_set_status_req.json
@@ -0,0 +1,11 @@
+{
+   "id": "c6d56fba-0cbd-11eb-926d-001fc69cc946",
+   "jsonrpc": "2.0",
+   "method": "admin_host_set_status",
+   "params": {
+      "operation": "$operation",
+      "host": ["$host"],
+      "reason": "manual",
+      "time": "100"
+   }
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_1.json b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_1.json
new file mode 100644
index 0000000..7be3e88
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_1.json
@@ -0,0 +1,14 @@
+{
+   "id":"38a9cc7e-5d41-4415-b74f-487ee1f01217",
+   "jsonrpc":"2.0",
+   "method":"admin_lookup_records",
+   "params":[
+      {
+         "record_name":"$record_name",
+         "rec_types":[
+            "1",
+            "16"
+         ]
+      }
+   ]
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_invalid_rec.json b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_invalid_rec.json
new file mode 100644
index 0000000..980796f
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_invalid_rec.json
@@ -0,0 +1,14 @@
+{
+   "id":"6ec0e08a-c9e4-4d95-b65b-294eacd7f13d",
+   "jsonrpc":"2.0",
+   "method":"admin_lookup_records",
+   "params":[
+      {
+         "record_name":"some.invalid.record.name",
+         "rec_types":[
+            "1",
+            "16"
+         ]
+      }
+   ]
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_metric.json b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_metric.json
new file mode 100644
index 0000000..78d2e77
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_metric.json
@@ -0,0 +1,11 @@
+{
+   "id":"5dee0f4d-94b9-4132-a6d8-f7924d3a6dac",
+   "jsonrpc":"2.0",
+   "method":"admin_lookup_records",
+   "params":[
+      {
+         "rec_types": [2, 4, 32],
+         "record_name_regex": "$record_name_regex"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_multiple.json b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_multiple.json
new file mode 100644
index 0000000..c380d5b
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_lookup_records_req_multiple.json
@@ -0,0 +1,18 @@
+{
+   "id":"38a9cc7e-5d41-4415-b74f-487ee1f01217",
+   "jsonrpc":"2.0",
+   "method":"admin_lookup_records",
+   "params":[
+      {
+         "record_name":"$record_name",
+         "rec_types":[
+            "1",
+            "16"
+         ]
+      },
+      {
+         "rec_types": [987],
+         "record_name": "$record_name"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_plugin_send_basic_msg_req.json b/tests/gold_tests/jsonrpc/json/admin_plugin_send_basic_msg_req.json
new file mode 100644
index 0000000..0b0bd96
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_plugin_send_basic_msg_req.json
@@ -0,0 +1,9 @@
+{
+   "id":"dd0ac6ad-1afc-4db6-b584-a2a02990940f",
+   "jsonrpc":"2.0",
+   "method":"admin_plugin_send_basic_msg",
+   "params":{
+      "tag":"some_tag",
+      "data":"some_data"
+   }
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/admin_storage_x_device_status_req.json b/tests/gold_tests/jsonrpc/json/admin_storage_x_device_status_req.json
new file mode 100644
index 0000000..bbc420f
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/admin_storage_x_device_status_req.json
@@ -0,0 +1,8 @@
+{
+   "id":"9893668b-8c58-477e-a962-39904247557b",
+   "jsonrpc":"2.0",
+   "method":"$method",
+   "params":[
+      "$device"
+   ]
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/json/method_call_no_params.json b/tests/gold_tests/jsonrpc/json/method_call_no_params.json
new file mode 100644
index 0000000..21c6d3a
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/json/method_call_no_params.json
@@ -0,0 +1,5 @@
+{
+   "id":"0ea2672f-b369-4d99-8d46-98c3fadc152d",
+   "jsonrpc":"2.0",
+   "method":"$method"
+}
\ No newline at end of file
diff --git a/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py b/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py
new file mode 100644
index 0000000..2591bd6
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py
@@ -0,0 +1,198 @@
+'''
+JSONRPC Schema test. This test will run a basic request/response schema validation.
+'''
+#  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.
+
+
+import os
+import sys
+import tempfile
+from string import Template
+
+Test.Summary = 'Test jsonrpc admin API'
+
+
+# set the schema folder.
+schema_folder = os.path.join(Test.TestDirectory, '..', '..', '..', "mgmt", "rpc", "schema")
+
+
+def substitute_context_in_file(process, file, context):
+    '''
+    Perform substitution based on the passed context dict. This function will return a new path for the substituted file.
+    '''
+    if os.path.isdir(file):
+        raise ValueError(f"Mapping substitution not supported for directories.")
+
+    with open(os.path.join(process.TestDirectory, file), 'r') as req_file:
+        req_template = Template(req_file.read())
+        req_content = req_template.substitute(context)
+        tf = tempfile.NamedTemporaryFile(delete=False, dir=process.RunDirectory, suffix=f"_{os.path.basename(file)}")
+        file = tf.name
+        with open(file, "w") as new_req_file:
+            new_req_file.write(req_content)
+
+    return file
+
+
+def add_testrun_for_jsonrpc_request(
+        test_description,
+        request_file_name,
+        params_schema_file_name=None,
+        result_schema_file_name=None,
+        context=None):
+    '''
+    Simple wrapper around the AddJsonRPCClientRequest method.
+
+    context:
+      This can be used if a template substitution is needed in the request file.
+
+    stdout_testers:
+      Testers to be run on the output stream.
+
+    params_schema_file_name:
+        Schema file to validate the request 'params' field.
+
+    result_schema_file_name:
+        Schema file to validate the response 'result' field.
+    '''
+    tr = Test.AddTestRun(test_description)
+    tr.Setup.Copy(request_file_name)
+
+    if context:
+        request_file_name = substitute_context_in_file(tr, request_file_name, context)
+
+    request_schema_file_name = os.path.join(schema_folder, "jsonrpc_request_schema.json")
+    tr.AddJsonRPCClientRequest(
+        ts,
+        file=os.path.join(
+            ts.RunDirectory,
+            os.path.basename(request_file_name)),
+        schema_file_name=request_schema_file_name,
+        params_field_schema_file_name=params_schema_file_name)
+
+    tr.Processes.Default.ReturnCode = 0
+
+    response_schema_file_name = os.path.join(schema_folder, "jsonrpc_response_schema.json")
+    tr.Processes.Default.Streams.stdout = Testers.JSONRPCResponseSchemaValidator(
+        schema_file_name=response_schema_file_name, result_field_schema_file_name=result_schema_file_name)
+
+    tr.StillRunningAfter = ts
+    return tr
+
+
+ts = Test.MakeATSProcess('ts')
+
+Test.testName = 'Basic JSONRPC API test'
+
+ts.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'rpc|filemanager|http|cache',
+    'proxy.config.jsonrpc.filename': "jsonrpc.yaml",  # We will be using this record to tests some RPC API.
+})
+
+# One of the API's will be checking the storage. Need this to get a response with content.
+storage_path = os.path.join(Test.RunDirectory, "ts", "storage")
+ts.Disk.storage_config.AddLine(f"{storage_path} 512M")
+
+
+# The following tests will only validate the jsonrpc message, it will not run any validation on the content of the 'result' or 'params'
+# of the jsonrpc message. This should be added once the schemas are avilable.
+
+# jsonrpc 2.0 schema file. This will not check the param fields.
+
+success_schema_file_name_name = os.path.join(schema_folder, "success_response_schema.json")
+# admin_lookup_records
+
+
+params_schema_file_name = os.path.join(schema_folder, "admin_lookup_records_params_schema.json")
+first = add_testrun_for_jsonrpc_request("Test admin_lookup_records",
+                                        request_file_name='json/admin_lookup_records_req_1.json',
+                                        params_schema_file_name=params_schema_file_name,
+                                        context={'record_name': 'proxy.config.jsonrpc.filename'})
+first.Processes.Default.StartBefore(ts)
+
+add_testrun_for_jsonrpc_request("Test admin_lookup_records w/error",
+                                request_file_name='json/admin_lookup_records_req_invalid_rec.json')
+
+add_testrun_for_jsonrpc_request("Test admin_lookup_records",
+                                request_file_name='json/admin_lookup_records_req_1.json',
+                                context={'record_name': 'proxy.config.jsonrpc.filename'})
+
+add_testrun_for_jsonrpc_request("Test admin_lookup_records w/error",
+                                request_file_name='json/admin_lookup_records_req_invalid_rec.json')
+
+add_testrun_for_jsonrpc_request("Test admin_lookup_records w/error",
+                                request_file_name='json/admin_lookup_records_req_multiple.json',
+                                context={'record_name': 'proxy.config.jsonrpc.filename'})
+
+add_testrun_for_jsonrpc_request("Test admin_lookup_records w/error",
+                                request_file_name='json/admin_lookup_records_req_metric.json',
+                                context={'record_name_regex': 'proxy.process.http.total_client_connections_ipv4*'})
+
+
+# admin_config_set_records
+add_testrun_for_jsonrpc_request("Test admin_lookup_records w/error", request_file_name='json/admin_config_set_records_req.json',
+                                context={'record_name': 'proxy.config.jsonrpc.filename', 'record_value': 'test_jsonrpc.yaml'})
+
+# admin_config_reload
+add_testrun_for_jsonrpc_request("Test admin_config_reload", request_file_name='json/admin_config_reload_req.json',
+                                result_schema_file_name=success_schema_file_name_name)
+
+# admin_clear_metrics_records
+add_testrun_for_jsonrpc_request("Clear admin_clear_metrics_records", request_file_name='json/admin_clear_metrics_records_req.json',
+                                context={'record_name': 'proxy.process.http.404_responses'})
+
+# admin_host_set_status
+add_testrun_for_jsonrpc_request("Test admin_host_set_status", request_file_name='json/admin_host_set_status_req.json',
+                                context={'operation': 'up', 'host': 'my.test.host.trafficserver.com'})
+
+# admin_host_set_status
+add_testrun_for_jsonrpc_request("Test admin_host_set_status", request_file_name='json/admin_host_set_status_req.json',
+                                context={'operation': 'down', 'host': 'my.test.host.trafficserver.com'})
+
+
+# admin_server_start_drain
+add_testrun_for_jsonrpc_request("Test admin_server_start_drain", request_file_name='json/method_call_no_params.json',
+                                context={'method': 'admin_server_start_drain'})
+
+add_testrun_for_jsonrpc_request("Test admin_server_start_drain",
+                                request_file_name='json/method_call_no_params.json',
+                                context={'method': 'admin_server_start_drain'})
+
+# admin_server_stop_drain
+add_testrun_for_jsonrpc_request("Test admin_server_stop_drain", request_file_name='json/method_call_no_params.json',
+                                context={'method': 'admin_server_stop_drain'})
+
+# admin_storage_get_device_status
+add_testrun_for_jsonrpc_request(
+    "Test admin_storage_get_device_status",
+    request_file_name='json/admin_storage_x_device_status_req.json',
+    context={
+        'method': 'admin_storage_get_device_status',
+        'device': f'{storage_path}/cache.db'})
+
+# admin_storage_set_device_offline
+add_testrun_for_jsonrpc_request(
+    "Test admin_storage_set_device_offline",
+    request_file_name='json/admin_storage_x_device_status_req.json',
+    context={
+        'method': 'admin_storage_set_device_offline',
+        'device': f'{storage_path}/cache.db'})
+
+# admin_plugin_send_basic_msg
+add_testrun_for_jsonrpc_request("Test admin_plugin_send_basic_msg", request_file_name='json/admin_plugin_send_basic_msg_req.json',
+                                result_schema_file_name=success_schema_file_name_name)
diff --git a/tests/gold_tests/basic/basic-manager.test.py b/tests/gold_tests/jsonrpc/plugins/Makefile.inc
similarity index 66%
rename from tests/gold_tests/basic/basic-manager.test.py
rename to tests/gold_tests/jsonrpc/plugins/Makefile.inc
index 4a7e238..33ab4eb 100644
--- a/tests/gold_tests/basic/basic-manager.test.py
+++ b/tests/gold_tests/jsonrpc/plugins/Makefile.inc
@@ -1,5 +1,3 @@
-'''
-'''
 #  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
@@ -16,16 +14,14 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-Test.Summary = '''
-Test that Trafficserver starts with default configurations.
-'''
+noinst_LTLIBRARIES += gold_tests/jsonrpc/plugins/jsonrpc_plugin_handler_test.la
+gold_tests_jsonrpc_plugins_jsonrpc_plugin_handler_test_la_SOURCES = gold_tests/jsonrpc/plugins/jsonrpc_plugin_handler_test.cc
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+AM_CPPFLAGS += \
+  -I$(abs_top_srcdir)/mgmt \
+  @YAMLCPP_INCLUDES@
 
-t = Test.AddTestRun("Test traffic server started properly")
-t.StillRunningAfter = Test.Processes.ts
+gold_tests_jsonrpc_plugins_jsonrpc_plugin_handler_test_la_LDFLAGS = \
+  $(AM_LDFLAGS)
 
-p = t.Processes.Default
-p.Command = "curl http://127.0.0.1:{0}".format(ts.Variables.port)
-p.ReturnCode = 0
-p.StartBefore(Test.Processes.ts)
+
diff --git a/tests/gold_tests/jsonrpc/plugins/jsonrpc_plugin_handler_test.cc b/tests/gold_tests/jsonrpc/plugins/jsonrpc_plugin_handler_test.cc
new file mode 100644
index 0000000..eec8b07
--- /dev/null
+++ b/tests/gold_tests/jsonrpc/plugins/jsonrpc_plugin_handler_test.cc
@@ -0,0 +1,312 @@
+/** @file
+
+  Test JSONRPC method and notification handling inside a plugin.
+
+  @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 <ts/ts.h>
+#include <string_view>
+#include <thread>
+#include <algorithm>
+#include <fstream>
+
+#include "yaml-cpp/yaml.h"
+#include "tscore/ts_file.h"
+#include "tscore/Errata.h"
+#include "tscore/I_Layout.h"
+#include "tscore/BufferWriter.h"
+#include "rpc/jsonrpc/JsonRPC.h"
+
+namespace
+{
+constexpr char PLUGIN_NAME[] = "jsonrpc_plugin_handler_test";
+
+const std::string MY_YAML_VERSION{"0.7.0"};
+const std::string RPC_PROVIDER_NAME{"RPC Plugin test"};
+} // namespace
+
+namespace
+{
+void
+test_join_hosts_method(const char *id, TSYaml p)
+{
+  TSDebug(PLUGIN_NAME, "Got a call! id: %s", id);
+  YAML::Node params = *reinterpret_cast<YAML::Node *>(p);
+
+  // This handler errors.
+  enum HandlerErrors { NO_HOST_ERROR = 10001, EMPTY_HOSTS_ERROR, UNKNOWN_ERROR };
+  try {
+    std::vector<std::string> hosts;
+    if (auto node = params["hosts"]) {
+      hosts = node.as<std::vector<std::string>>();
+    } else {
+      // We can't continue. Notify the RPC manager.
+      std::string descr{"No host provided"};
+      TSRPCHandlerError(NO_HOST_ERROR, descr.c_str(), descr.size());
+      return;
+    }
+
+    if (0 == hosts.size()) {
+      // We can't continue. Notify the RPC manager.
+      std::string descr{"At least one host should be provided"};
+      TSRPCHandlerError(EMPTY_HOSTS_ERROR, descr.c_str(), descr.size());
+      return;
+    }
+
+    std::string join;
+    std::for_each(std::begin(hosts), std::end(hosts), [&join](auto &&s) { join += s; });
+    YAML::Node resp;
+    resp["join"] = join;
+    // All done. Notify the RPC manager.
+    TSRPCHandlerDone(reinterpret_cast<TSYaml>(&resp));
+  } catch (YAML::Exception const &ex) {
+    TSDebug(PLUGIN_NAME, "Oops, something went wrong: %s", ex.what());
+    std::string descr{ex.what()};
+    TSRPCHandlerError(UNKNOWN_ERROR, descr.c_str(), descr.size());
+  }
+}
+
+// It's a notificaion, we do not care to respond back to the JSONRPC manager.
+void
+test_join_hosts_notification(TSYaml p)
+{
+  TSDebug(PLUGIN_NAME, "Got a call!");
+  try {
+    YAML::Node params = *reinterpret_cast<YAML::Node *>(p);
+
+    std::vector<std::string> hosts;
+    if (auto hosts = params["hosts"]) {
+      hosts = hosts.as<std::vector<std::string>>();
+    }
+    if (0 == hosts.size()) {
+      TSDebug(PLUGIN_NAME, "No hosts field provided. Nothing we can do. No response back.");
+      return;
+    }
+    std::string join;
+    std::for_each(std ::begin(hosts), std::end(hosts), [&join](auto &&s) { join += s; });
+    TSDebug(PLUGIN_NAME, "Notification properly handled: %s", join.c_str());
+  } catch (YAML::Exception const &ex) {
+    TSDebug(PLUGIN_NAME, "Oops, something went wrong: %s", ex.what());
+  }
+}
+} // namespace
+namespace
+{
+// Incoming host info structure.
+struct HostItem {
+  std::string name;
+  std::string status;
+};
+
+int
+CB_handle_rpc_io_call(TSCont contp, TSEvent event, void *data)
+{
+  namespace fs = ts::file;
+
+  TSDebug(PLUGIN_NAME, "Working on the update now");
+  YAML::Node params = *static_cast<YAML::Node *>(TSContDataGet(contp));
+
+  // This handler errors.
+  enum HandlerErrors { INVALID_PARAM_TYPE = 10010, INVALID_HOST_PARAM_TYPE, FILE_UPDATE_ERROR, UNKNOWN_ERROR };
+
+  // we only care for a map type {}
+  if (params.Type() != YAML::NodeType::Map) {
+    std::string descr{"Handler is expecting a map."};
+    TSRPCHandlerError(INVALID_PARAM_TYPE, descr.c_str(), descr.size());
+    return TS_SUCCESS;
+  }
+
+  // we want to keep track of the work done! This will become part of the response
+  // and it will be validated on the client side. The whole test validation is base
+  // on this.
+  int updatedHosts{0};
+  int addedHosts{0};
+
+  std::vector<HostItem> incHosts;
+  if (auto &&passedHosts = params["hosts"]; passedHosts.Type() == YAML::NodeType::Sequence) {
+    // fill in
+    for (auto &&h : passedHosts) {
+      std::string name, status;
+      if (auto &&n = h["name"]) {
+        name = n.as<std::string>();
+      }
+
+      if (auto &&s = h["status"]) {
+        status = s.as<std::string>();
+      }
+      incHosts.push_back({name, status});
+    }
+  } else {
+    std::string descr{"not a sequence, we expect a list of hosts"};
+    TSRPCHandlerError(INVALID_HOST_PARAM_TYPE, descr.c_str(), descr.size());
+    return TS_SUCCESS;
+  }
+
+  // Basic stuffs here.
+  // We open the file if exist, we update/add the host in the structure. For simplicity we do not delete anything.
+  fs::path sandbox  = ts::file::current_path();
+  fs::path dumpFile = sandbox / "my_test_plugin_dump.yaml";
+  bool newFile{false};
+  if (!ts::file::exists(dumpFile)) {
+    newFile = true;
+  }
+
+  // handle function to add new hosts to a node.
+  auto add_host = [](std::string const &name, std::string const &status, YAML::Node &out) -> void {
+    YAML::Node newHost;
+    newHost["name"]   = name;
+    newHost["status"] = status;
+    out.push_back(newHost);
+  };
+
+  YAML::Node dump;
+  if (!newFile) {
+    try {
+      dump = YAML::LoadFile(dumpFile.c_str());
+      if (dump.IsSequence()) {
+        std::vector<HostItem> tobeAdded;
+        for (auto &&incHost : incHosts) {
+          auto search = std::find_if(std::begin(dump), std::end(dump), [&incHost](YAML::Node const &node) {
+            if (auto &&n = node["name"]) {
+              return incHost.name == n.as<std::string>();
+            } else {
+              throw std::runtime_error("We couldn't find 'name' field.");
+            }
+          });
+
+          if (search != std::end(dump)) {
+            (*search)["status"] = incHost.status;
+            ++updatedHosts;
+          } else {
+            add_host(incHost.name, incHost.status, dump);
+            ++addedHosts;
+          }
+        }
+      }
+    } catch (YAML::Exception const &e) {
+      std::string buff;
+      ts::bwprint(buff, "Error during file handling: {}", e.what());
+      TSRPCHandlerError(UNKNOWN_ERROR, buff.c_str(), buff.size());
+      return TS_SUCCESS;
+    }
+  } else {
+    for (auto &&incHost : incHosts) {
+      add_host(incHost.name, incHost.status, dump);
+      ++addedHosts;
+    }
+  }
+
+  // Dump it..
+  YAML::Emitter out;
+  out << dump;
+
+  fs::path tmpFile = sandbox / "tmpfile.yaml";
+  std::ofstream ofs(tmpFile.c_str());
+  ofs << out.c_str();
+  ofs.close();
+
+  std::error_code ec;
+  if (fs::copy(tmpFile, dumpFile, ec); ec) {
+    std::string buff;
+    ts::bwprint(buff, "Error during file handling: {}, {}", ec.value(), ec.message());
+    TSRPCHandlerError(FILE_UPDATE_ERROR, buff.c_str(), buff.size());
+    return TS_SUCCESS;
+  }
+
+  // clean up the temp file if possible.
+  if (fs::remove(tmpFile, ec); ec) {
+    TSDebug(PLUGIN_NAME, "Temp file could not be removed: %s", tmpFile.c_str());
+  }
+
+  // make the response. For complex structures YAML::convert<T>::encode() would be the preferred way.
+  YAML::Node resp;
+  resp["updatedHosts"] = updatedHosts;
+  resp["addedHosts"]   = addedHosts;
+  resp["dumpFile"]     = dumpFile.c_str(); // In case you need this
+
+  // we are done!!
+  TSContDestroy(contp);
+  TSRPCHandlerDone(reinterpret_cast<TSYaml>(&resp));
+  return TS_SUCCESS;
+}
+
+// Perform a field updated on a yaml file. Host will be added or updated
+void
+test_io_on_et_task(const char *id, TSYaml p)
+{
+  // A possible scenario would be that a handler needs to perform a "heavy" operation or that the handler
+  // is not yet ready to perform the operation when is called, under this scenarios(and some others)
+  // we can use the RPC API to easily achieve this and respond just when we are ready.
+  TSCont c = TSContCreate(CB_handle_rpc_io_call, TSMutexCreate());
+  TSContDataSet(c, p);
+  TSContScheduleOnPool(c, 1000 /* no particular reason */, TS_THREAD_POOL_TASK);
+}
+} // namespace
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = PLUGIN_NAME;
+  info.vendor_name   = "Apache Software Foundation";
+  info.support_email = "dev@trafficserver.apache.org";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+  }
+
+  // Check-in to make sure we are compliant with the YAML version in TS.
+  TSRPCProviderHandle rpcRegistrationInfo =
+    TSRPCRegister(RPC_PROVIDER_NAME.c_str(), RPC_PROVIDER_NAME.size(), MY_YAML_VERSION.c_str(), MY_YAML_VERSION.size());
+
+  if (rpcRegistrationInfo == nullptr) {
+    TSError("[%s] RPC handler registration failed, yaml version not supported.", PLUGIN_NAME);
+  }
+
+  TSRPCHandlerOptions opt{{true}};
+  std::string method_name{"test_join_hosts_method"};
+  if (TSRPCRegisterMethodHandler(method_name.c_str(), method_name.size(), test_join_hosts_method, rpcRegistrationInfo, &opt) ==
+      TS_ERROR) {
+    TSDebug(PLUGIN_NAME, "%s failed to register", method_name.c_str());
+  } else {
+    TSDebug(PLUGIN_NAME, "%s successfully registered", method_name.c_str());
+  }
+
+  // TASK thread.
+  method_name = "test_io_on_et_task";
+  if (TSRPCRegisterMethodHandler(method_name.c_str(), method_name.size(), test_io_on_et_task, rpcRegistrationInfo, &opt) ==
+      TS_ERROR) {
+    TSDebug(PLUGIN_NAME, "%s failed to register", method_name.c_str());
+  } else {
+    TSDebug(PLUGIN_NAME, "%s successfully registered", method_name.c_str());
+  }
+
+  // Notification
+  TSRPCHandlerOptions nOpt{{false}};
+  method_name = "test_join_hosts_notification";
+  if (TSRPCRegisterNotificationHandler(method_name.c_str(), method_name.size(), test_join_hosts_notification, rpcRegistrationInfo,
+                                       &nOpt) == TS_ERROR) {
+    TSDebug(PLUGIN_NAME, "%s failed to register", method_name.c_str());
+  } else {
+    TSDebug(PLUGIN_NAME, "%s successfully registered", method_name.c_str());
+  }
+
+  TSDebug(PLUGIN_NAME, "Test Plugin Initialized.");
+}
diff --git a/tests/gold_tests/logging/log-filenames.test.py b/tests/gold_tests/logging/log-filenames.test.py
index 5f94f32..d3913d2 100644
--- a/tests/gold_tests/logging/log-filenames.test.py
+++ b/tests/gold_tests/logging/log-filenames.test.py
@@ -35,8 +35,7 @@
     # The default log names for the various system logs.
     default_log_data = {
         'diags': 'diags.log',
-        'error': 'error.log',
-        'manager': 'manager.log'
+        'error': 'error.log'
     }
 
     def __init__(self, description, log_data=default_log_data):
@@ -50,11 +49,11 @@
             MakeATSProcess extension.
         '''
         self.__description = description
-        self.ts = self.__configure_traffic_manager(log_data)
+        self.ts = self.__configure_traffic_server(log_data)
         self.tr = self.__configure_traffic_TestRun(description)
         self.__configure_await_TestRun(self.sentinel_log_path)
 
-    def __configure_traffic_manager(self, log_data):
+    def __configure_traffic_server(self, log_data):
         ''' Common ATS configuration logic.
 
         Args:
@@ -62,12 +61,11 @@
             MakeATSProcess extension.
 
         Return:
-            The traffic_manager process.
+            The traffic_server process.
         '''
         self._ts_name = f"ts{LogFilenamesTest.__ts_counter}"
         LogFilenamesTest.__ts_counter += 1
-        self.ts = Test.MakeATSProcess(self._ts_name, command="traffic_manager",
-                                      use_traffic_out=False, log_data=log_data)
+        self.ts = Test.MakeATSProcess(self._ts_name, use_traffic_out=False, log_data=log_data)
         self.ts.Disk.records_config.update({
             'proxy.config.diags.debug.enabled': 0,
             'proxy.config.diags.debug.tags': 'log',
@@ -90,7 +88,7 @@
             logging:
               formats:
                 - name: url_and_return_code
-                  format: "%<cqu>: %<pssc>"
+                  format: "%<pqu>: %<pssc>"
               logs:
                 - filename: {self.sentinel_log_filename}
                   format: url_and_return_code
@@ -159,13 +157,9 @@
         return self.custom_log_path
 
     def set_log_expectations(self):
-        ''' Configure sanity checks for each of the log types (manager, error,
+        ''' Configure sanity checks for each of the log types (diags, error,
         etc.) to verify they are emitting the expected content.
         '''
-        manager_path = self.ts.Disk.manager_log.AbsPath
-        self.ts.Disk.manager_log.Content += Testers.ContainsExpression(
-            "Launching ts process",
-            f"{manager_path} should contain traffic_manager log messages")
 
         diags_path = self.ts.Disk.diags_log.AbsPath
         self.ts.Disk.diags_log.Content += Testers.ContainsExpression(
@@ -185,47 +179,33 @@
 
 class DefaultNamedTest(LogFilenamesTest):
     ''' Verify that if custom names are not configured, then the default
-    'diags.log', 'manager.log', and 'error.log' are written to.
+    'diags.log' and 'error.log' are written to.
     '''
 
     def __init__(self):
         super().__init__('default log filename configuration')
 
-        # For these tests, more important than the listening port is the
-        # existence of the log files. In particular, it can take a few seconds
-        # for traffic_manager to open diags.log.
-        self.diags_log = self.ts.Disk.diags_log.AbsPath
-        self.ts.Ready = When.FileExists(self.diags_log)
-
         self.configure_named_custom_log('my_custom_log')
         self.set_log_expectations()
 
 
 class CustomNamedTest(LogFilenamesTest):
-    ''' Verify that the user can assign custom filenames to manager.log, etc.
+    ''' Verify that the user can assign custom filenames to diags.log, etc.
     '''
 
     def __init__(self):
         log_data = {
             'diags': 'my_diags.log',
-            'error': 'my_error.log',
-            'manager': 'my_manager.log'
+            'error': 'my_error.log'
         }
         super().__init__('specify log filename configuration', log_data)
 
-        # Configure custom names for manager.log, etc.
+        # Configure custom names for diags.log, etc.
         self.ts.Disk.records_config.update({
-            'proxy.node.config.manager_log_filename': 'my_manager.log',
             'proxy.config.diags.logfile.filename': 'my_diags.log',
             'proxy.config.error.logfile.filename': 'my_error.log',
         })
 
-        # For these tests, more important than the listening port is the
-        # existence of the log files. In particular, it can take a few seconds
-        # for traffic_manager to open diags.log.
-        self.diags_log = self.ts.Disk.diags_log.AbsPath
-        self.ts.Ready = When.FileExists(self.diags_log)
-
         self.configure_named_custom_log('my_custom_log')
         self.set_log_expectations()
 
@@ -238,14 +218,12 @@
 
         log_data = {
             'diags': 'stdout',
-            'error': 'stdout',
-            'manager': 'stdout'
+            'error': 'stdout'
         }
         super().__init__('specify logs to go to stdout', log_data)
 
-        # Configure custom names for manager.log, etc.
+        # Configure custom names for diags.log, etc.
         self.ts.Disk.records_config.update({
-            'proxy.node.config.manager_log_filename': 'stdout',
             'proxy.config.diags.logfile.filename': 'stdout',
             'proxy.config.error.logfile.filename': 'stdout',
         })
@@ -267,14 +245,12 @@
 
         log_data = {
             'diags': 'stderr',
-            'error': 'stderr',
-            'manager': 'stderr'
+            'error': 'stderr'
         }
         super().__init__('specify logs to go to stderr', log_data)
 
-        # Configure custom names for manager.log, etc.
+        # Configure custom names for diags.log, etc.
         self.ts.Disk.records_config.update({
-            'proxy.node.config.manager_log_filename': 'stderr',
             'proxy.config.diags.logfile.filename': 'stderr',
             'proxy.config.error.logfile.filename': 'stderr',
         })
diff --git a/tests/gold_tests/logging/log_retention.test.py b/tests/gold_tests/logging/log_retention.test.py
index 4531556..0ca0702 100644
--- a/tests/gold_tests/logging/log_retention.test.py
+++ b/tests/gold_tests/logging/log_retention.test.py
@@ -50,7 +50,7 @@
     __ts_counter = 0
     __server_is_started = False
 
-    def __init__(self, records_config, run_description, command="traffic_manager"):
+    def __init__(self, records_config, run_description, command="traffic_server"):
         """
         Create a TestLogRetention instance.
         """
@@ -101,7 +101,7 @@
         cls.__server = server
         return cls.__server
 
-    def __create_ts(self, records_config, command="traffic_manager"):
+    def __create_ts(self, records_config, command="traffic_server"):
         """
         Create an ATS process.
 
diff --git a/tests/gold_tests/logging/sigusr2.test.py b/tests/gold_tests/logging/sigusr2.test.py
index fae67e0..a93b4ee 100644
--- a/tests/gold_tests/logging/sigusr2.test.py
+++ b/tests/gold_tests/logging/sigusr2.test.py
@@ -20,8 +20,7 @@
 import os
 import sys
 
-
-TRAFFIC_MANAGER_PID_SCRIPT = 'ts_process_handler.py'
+TS_PID_SCRIPT = 'ts_process_handler.py'
 
 
 class Sigusr2Test:
@@ -34,12 +33,12 @@
 
     def __init__(self):
         self.server = self.__configure_server()
-        self.ts = self.__configure_traffic_manager()
+        self.ts = self.__configure_traffic_server()
 
-    def __configure_traffic_manager(self):
+    def __configure_traffic_server(self):
         self._ts_name = "sigusr2_ts{}".format(Sigusr2Test.__ts_counter)
         Sigusr2Test.__ts_counter += 1
-        self.ts = Test.MakeATSProcess(self._ts_name, command="traffic_manager")
+        self.ts = Test.MakeATSProcess(self._ts_name)
         self.ts.Disk.records_config.update({
             'proxy.config.http.wait_for_cache': 1,
             'proxy.config.diags.debug.enabled': 1,
@@ -51,20 +50,13 @@
             'proxy.config.log.auto_delete_rolled_files': 0,
         })
 
-        # For this test, more important than the listening port is the existence of the
-        # log files. In particular, it can take a few seconds for traffic_manager to
-        # open diags.log.
         self.diags_log = self.ts.Disk.diags_log.AbsPath
-        self.ts.Ready = When.FileExists(self.diags_log)
 
         # Add content handles for the rotated logs.
         self.rotated_diags_log = self.diags_log + "_old"
         self.ts.Disk.File(self.rotated_diags_log, id="diags_log_old")
 
         self.log_dir = os.path.dirname(self.diags_log)
-        self.manager_log = os.path.join(self.log_dir, "manager.log")
-        self.rotated_manager_log = self.manager_log + "_old"
-        self.ts.Disk.File(self.rotated_manager_log, id="manager_log_old")
 
         self.ts.Disk.remap_config.AddLine(
             'map http://127.0.0.1:{0} http://127.0.0.1:{1}'.format(
@@ -74,7 +66,7 @@
             logging:
               formats:
                 - name: has_path
-                  format: "%<cqu>: %<sssc>"
+                  format: "%<pqu>: %<sssc>"
               logs:
                 - filename: test_rotation
                   format: has_path
@@ -105,10 +97,10 @@
 
     def get_sigusr2_signal_command(self):
         """
-        Return the command that will send a USR2 signal to the traffic manager
+        Return the command that will send a USR2 signal to the traffic server
         process.
         """
-        return (f"{sys.executable} {TRAFFIC_MANAGER_PID_SCRIPT} --parent "
+        return (f"{sys.executable} {TS_PID_SCRIPT} "
                 f"--signal SIGUSR2 {self._ts_name}")
 
 
@@ -116,44 +108,31 @@
 Verify support of external log rotation via SIGUSR2.
 '''
 
-Test.Setup.CopyAs(TRAFFIC_MANAGER_PID_SCRIPT, Test.RunDirectory)
+Test.Setup.CopyAs(TS_PID_SCRIPT, Test.RunDirectory)
 
 #
 # Test 1: Verify SIGUSR2 behavior for system logs.
 #
-tr1 = Test.AddTestRun("Verify system logs (manager.log, etc.) can be rotated")
+tr1 = Test.AddTestRun("Verify system logs can be rotated")
 
-# Configure Traffic Manager/Server.
+# Configure Server.
 diags_test = Sigusr2Test()
 
 # Configure our rotation processes.
 rotate_diags_log = tr1.Processes.Process("rotate_diags_log", "mv {} {}".format(
     diags_test.diags_log, diags_test.rotated_diags_log))
-rotate_manager_log = tr1.Processes.Process("rotate_manager_log", "mv {} {}".format(
-    diags_test.manager_log, diags_test.rotated_manager_log))
 
-# Configure the signaling of SIGUSR2 to traffic_manager.
+# Configure the signaling of SIGUSR2 to traffic_server.
 tr1.Processes.Default.Command = diags_test.get_sigusr2_signal_command()
 tr1.Processes.Default.Return = 0
 tr1.Processes.Default.Ready = When.FileExists(diags_test.diags_log)
 
 # Configure process order.
 tr1.Processes.Default.StartBefore(rotate_diags_log)
-rotate_diags_log.StartBefore(rotate_manager_log)
-rotate_manager_log.StartBefore(diags_test.ts)
+rotate_diags_log.StartBefore(diags_test.ts)
 tr1.StillRunningAfter = diags_test.ts
 tr1.StillRunningAfter = diags_test.server
 
-# manager.log should have been rotated. Check for the expected content in the
-# old file and the newly created file.
-diags_test.ts.Disk.manager_log_old.Content += Testers.ContainsExpression(
-    "received SIGUSR2, rotating the logs",
-    "manager.log_old should explain that SIGUSR2 was passed to it")
-
-diags_test.ts.Disk.manager_log.Content += Testers.ContainsExpression(
-    "Reseated manager.log",
-    "The new manager.log should indicate the newly opened manager.log")
-
 # diags.log should have been rotated. The old one had the reference to traffic
 # server running, this new one shouldn't. But it should indicate that the new
 # diags.log was opened.
@@ -215,7 +194,7 @@
 #   1. curl /first. The entry should be logged to current log which will be _old.
 #   2. mv the log to _old.
 #   3. curl /second. The entry should end up in _old log.
-#   4. Send a SIGUSR2 to traffic_manager. The log should be recreated.
+#   4. Send a SIGUSR2 to traffic_server. The log should be recreated.
 #   5. curl /third. The entry should end up in the new, non-old, log file.
 #
 tr2.Processes.Default.StartBefore(third_curl_ready)
diff --git a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests.test.py b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests.test.py
index 44a39af..573a133 100644
--- a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests.test.py
+++ b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests.test.py
@@ -34,7 +34,7 @@
 Test.testName = "cache_range_requests"
 
 # Define and configure ATS
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # Define and configure origin server
 server = Test.MakeOriginServer("server", lookup_key="{%uuid}")
@@ -223,7 +223,7 @@
 ])
 
 # cache debug
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-parentselection-key')
 
 # minimal configuration
 ts.Disk.records_config.update({
diff --git a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cache_complete_responses.test.py b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cache_complete_responses.test.py
index 371ed64..12256b8 100644
--- a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cache_complete_responses.test.py
+++ b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cache_complete_responses.test.py
@@ -59,7 +59,7 @@
     slice_body += 'x'
 
 # Define and configure ATS
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # Define and configure origin server
 server = Test.MakeOriginServer("server", lookup_key="{%UID}")
@@ -228,7 +228,7 @@
 ])
 
 # cache debug
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key')
 
 # enable debug
 ts.Disk.records_config.update({
diff --git a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey.test.py b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey.test.py
index 84374bc..cf59740 100644
--- a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey.test.py
+++ b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey.test.py
@@ -34,7 +34,7 @@
 Test.testName = "cache_range_requests_cachekey"
 
 # Define and configure ATS, enable traffic_ctl config reload
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # Define and configure origin server
 server = Test.MakeOriginServer("server", lookup_key="{%uuid}")
@@ -153,7 +153,7 @@
 )
 
 # cache debug
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
 
 # minimal configuration
 ts.Disk.records_config.update({
diff --git a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey_global.test.py b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey_global.test.py
index 13baca6..48cc442 100644
--- a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey_global.test.py
+++ b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_cachekey_global.test.py
@@ -34,7 +34,7 @@
 Test.testName = "cache_range_requests_cachekey_global"
 
 # Define and configure ATS, enable traffic_ctl config reload
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # Define and configure origin server
 server = Test.MakeOriginServer("server", lookup_key="{%uuid}")
@@ -152,7 +152,7 @@
 ts.Disk.plugin_config.AddLines([
     'cachekey.so --include-headers=Range --static-prefix=foo',
     'cache_range_requests.so --no-modify-cachekey',
-    'xdebug.so',
+    'xdebug.so --enable=x-cache,x-cache-key,x-parentselection-key',
 ])
 
 # minimal configuration
diff --git a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_ims.test.py b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_ims.test.py
index eb96261..db4fb96 100644
--- a/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_ims.test.py
+++ b/tests/gold_tests/pluginTest/cache_range_requests/cache_range_requests_ims.test.py
@@ -35,7 +35,7 @@
 Test.testName = "cache_range_requests_ims"
 
 # Define and configure ATS
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # Define and configure origin server
 server = Test.MakeOriginServer("server")
@@ -97,7 +97,7 @@
 ])
 
 # cache debug
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
 
 # minimal configuration
 ts.Disk.records_config.update({
diff --git a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
index 0b1af25..8ba459a 100644
--- a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
+++ b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
@@ -36,7 +36,7 @@
 server.addResponse("sessionlog.json", request_header, response_header)
 
 # Set up ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=1)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 
 # Set up ssl files
 ts.addSSLfile("ssl/server1.pem")
diff --git a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
index 334479c..dd3e891 100644
--- a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
+++ b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
@@ -25,7 +25,7 @@
 Test.SkipUnless(Condition.PluginExists('client_context_dump.so'))
 
 # Set up ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 
 # Set up ssl files
 ts.addSSLfile("ssl/one.com.pem")
diff --git a/tests/gold_tests/pluginTest/lua/lua_debug_tags.test.py b/tests/gold_tests/pluginTest/lua/lua_debug_tags.test.py
index 4803b9e..bfbe4a8 100644
--- a/tests/gold_tests/pluginTest/lua/lua_debug_tags.test.py
+++ b/tests/gold_tests/pluginTest/lua/lua_debug_tags.test.py
@@ -28,7 +28,7 @@
 
 Test.ContinueOnFail = False
 # Define default ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 ts.Disk.remap_config.AddLine(
     'map http://test http://127.0.0.1/ @plugin=tslua.so @pparam=tags.lua'
diff --git a/tests/gold_tests/pluginTest/lua/lua_states_stats.test.py b/tests/gold_tests/pluginTest/lua/lua_states_stats.test.py
index fb3efe3..d817924 100644
--- a/tests/gold_tests/pluginTest/lua/lua_states_stats.test.py
+++ b/tests/gold_tests/pluginTest/lua/lua_states_stats.test.py
@@ -28,7 +28,7 @@
 # Define default ATS
 server = Test.MakeOriginServer("server")
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 Test.testName = "Lua states and stats"
 
diff --git a/tests/gold_tests/pluginTest/money_trace/money_trace.test.py b/tests/gold_tests/pluginTest/money_trace/money_trace.test.py
index 5b35c14..b6fce39 100644
--- a/tests/gold_tests/pluginTest/money_trace/money_trace.test.py
+++ b/tests/gold_tests/pluginTest/money_trace/money_trace.test.py
@@ -27,7 +27,7 @@
 Test.testName = "money_trace remap"
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_server", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 
 # configure origin server
 server = Test.MakeOriginServer("server")
diff --git a/tests/gold_tests/pluginTest/money_trace/money_trace_global.test.py b/tests/gold_tests/pluginTest/money_trace/money_trace_global.test.py
index d7092d8..bb4fb83 100644
--- a/tests/gold_tests/pluginTest/money_trace/money_trace_global.test.py
+++ b/tests/gold_tests/pluginTest/money_trace/money_trace_global.test.py
@@ -29,7 +29,7 @@
 Test.testName = "money_trace global"
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_server", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 
 # configure origin server
 server = Test.MakeOriginServer("server")
diff --git a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate.test.py b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate.test.py
index 10fa65d..7014e5b 100644
--- a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate.test.py
+++ b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate.test.py
@@ -18,6 +18,8 @@
 
 import os
 import time
+from jsonrpc import Request
+
 Test.Summary = '''
 Test a basic regex_revalidate
 '''
@@ -42,7 +44,7 @@
 server = Test.MakeOriginServer("server")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 Test.testName = "regex_revalidate"
 Test.Setup.Copy("metrics.sh")
@@ -125,7 +127,7 @@
 server.addResponse("sessionlog.json", request_header_3, response_header_3)
 
 # Configure ATS server
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
 ts.Disk.plugin_config.AddLine(
     'regex_revalidate.so -d -c regex_revalidate.conf'
 )
@@ -194,9 +196,7 @@
 ])
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-tr.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket
-tr.Processes.Default.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.TimeOut = 5
 tr.TimeOut = 5
@@ -228,9 +228,7 @@
 ])
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-tr.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket
-tr.Processes.Default.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.TimeOut = 5
 tr.TimeOut = 5
@@ -265,9 +263,7 @@
 ])
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-tr.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket
-tr.Processes.Default.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.TimeOut = 5
 tr.TimeOut = 5
@@ -283,7 +279,8 @@
 # 12 Stats check
 tr = Test.AddTestRun("Check stats")
 tr.DelayStart = 5
-tr.Processes.Default.Command = "bash -c ./metrics.sh"
+# tr.Processes.Default.Command = f"bash -c './metrics.sh {ts.Disk.runroot_yaml.Name}'"
+tr.Processes.Default.Command = f"bash -c './metrics.sh'"
 tr.Processes.Default.Env = ts.Env
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py
index ea28b50..db8803a 100644
--- a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py
+++ b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py
@@ -18,6 +18,8 @@
 
 import os
 import time
+from jsonrpc import Request
+
 Test.Summary = '''
 regex_revalidate plugin test, MISS (refetch) functionality
 '''
@@ -36,7 +38,7 @@
 server = Test.MakeOriginServer("server")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 Test.testName = "regex_revalidate_miss"
 Test.Setup.Copy("metrics_miss.sh")
@@ -82,7 +84,7 @@
 server.addResponse("sessionlog.json", request_header_1, response_header_1)
 
 # Configure ATS server
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
 ts.Disk.plugin_config.AddLine(
     'regex_revalidate.so -d -c regex_revalidate.conf -l revalidate.log'
 )
@@ -139,17 +141,17 @@
 tr.Disk.File(regex_revalidate_conf_path + "_tr2", typename="ats:config").AddLine(path1_rule + ' MISS')
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-ps.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket
-ps.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 ps.ReturnCode = 0
 ps.TimeOut = 5
 tr.TimeOut = 5
+# Delay it so the reload can catch up the diff between config files timestamps.
+tr.DelayStart = 1
 
 # 3 Test - Revalidate path1
 tr = Test.AddTestRun("Revalidate MISS path1")
 ps = tr.Processes.Default
-tr.DelayStart = 5
+tr.DelayStart = 7
 ps.Command = curl_and_args + ' http://ats/path1'
 ps.ReturnCode = 0
 ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss response")
@@ -170,16 +172,17 @@
 tr.Disk.File(regex_revalidate_conf_path + "_tr5", typename="ats:config").AddLine(path1_rule + ' STALE')
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-ps.Command = 'traffic_ctl config reload'
-ps.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 ps.ReturnCode = 0
 ps.TimeOut = 5
 tr.TimeOut = 5
+# Delay it so the reload can catch up the diff between config files timestamps.
+tr.DelayStart = 1
 
 # 6 Test - Cache stale
 tr = Test.AddTestRun("Cache stale path1")
 ps = tr.Processes.Default
-tr.DelayStart = 5
+tr.DelayStart = 7
 ps.Command = curl_and_args + ' http://ats/path1'
 ps.ReturnCode = 0
 ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit-stale", "expected cache hit stale response")
@@ -192,16 +195,17 @@
 tr.Disk.File(regex_revalidate_conf_path + "_tr7", typename="ats:config").AddLine(path1_rule + ' MISS')
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-ps.Command = 'traffic_ctl config reload'
-ps.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 ps.ReturnCode = 0
 ps.TimeOut = 5
 tr.TimeOut = 5
+# Delay it so the reload can catch up the diff between config files timestamps.
+tr.DelayStart = 1
 
 # 8 Test - Cache stale
 tr = Test.AddTestRun("Cache stale path1")
 ps = tr.Processes.Default
-tr.DelayStart = 5
+tr.DelayStart = 7
 ps.Command = curl_and_args + ' http://ats/path1'
 ps.ReturnCode = 0
 ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss response")
@@ -214,16 +218,17 @@
 tr.Disk.File(regex_revalidate_conf_path + "_tr9", typename="ats:config").AddLine(path1_rule + ' MISSSTALE')
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-ps.Command = 'traffic_ctl config reload'
-ps.Env = ts.Env
+tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload())
 ps.ReturnCode = 0
 ps.TimeOut = 5
 tr.TimeOut = 5
+# Delay it so the reload can catch up the diff between config files timestamps.
+tr.DelayStart = 1
 
 # 10 Test - Cache stale
 tr = Test.AddTestRun("Cache stale path1")
 ps = tr.Processes.Default
-tr.DelayStart = 5
+tr.DelayStart = 7
 ps.Command = curl_and_args + ' http://ats/path1'
 ps.ReturnCode = 0
 ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit-fresh", "expected cache hit response")
@@ -232,7 +237,7 @@
 # 11 Stats check
 tr = Test.AddTestRun("Check stats")
 tr.DelayStart = 5
-tr.Processes.Default.Command = "bash -c ./metrics_miss.sh"
+tr.Processes.Default.Command = f"bash -c ./metrics_miss.sh"
 tr.Processes.Default.Env = ts.Env
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py
index 04433cc..88cdf83 100644
--- a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py
+++ b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py
@@ -35,7 +35,7 @@
 server = Test.MakeOriginServer("server")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 # **testname is required**
 testName = "regex_revalidate_state"
diff --git a/tests/gold_tests/pluginTest/remap_stats/remap_stats.test.py b/tests/gold_tests/pluginTest/remap_stats/remap_stats.test.py
index 3d09d95..69927df 100644
--- a/tests/gold_tests/pluginTest/remap_stats/remap_stats.test.py
+++ b/tests/gold_tests/pluginTest/remap_stats/remap_stats.test.py
@@ -30,7 +30,7 @@
                    "timestamp": "1469733493.993", "body": ""}
 server.addResponse("sessionlog.json", request_header, response_header)
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 ts.Disk.plugin_config.AddLine('remap_stats.so')
 
diff --git a/tests/gold_tests/pluginTest/remap_stats/remap_stats_post.test.py b/tests/gold_tests/pluginTest/remap_stats/remap_stats_post.test.py
index 4799974..54a9146 100644
--- a/tests/gold_tests/pluginTest/remap_stats/remap_stats_post.test.py
+++ b/tests/gold_tests/pluginTest/remap_stats/remap_stats_post.test.py
@@ -30,7 +30,7 @@
                    "timestamp": "1469733493.993", "body": ""}
 server.addResponse("sessionlog.json", request_header, response_header)
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 ts.Disk.plugin_config.AddLine('remap_stats.so --post-remap-host')
 
diff --git a/tests/gold_tests/pluginTest/slice/slice.test.py b/tests/gold_tests/pluginTest/slice/slice.test.py
index d3e09c4..5382c9e 100644
--- a/tests/gold_tests/pluginTest/slice/slice.test.py
+++ b/tests/gold_tests/pluginTest/slice/slice.test.py
@@ -34,7 +34,7 @@
 server = Test.MakeOriginServer("server")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # default root
 request_header_chk = {"headers":
diff --git a/tests/gold_tests/pluginTest/slice/slice_error.test.py b/tests/gold_tests/pluginTest/slice/slice_error.test.py
index 926f96f..3ed81c6 100644
--- a/tests/gold_tests/pluginTest/slice/slice_error.test.py
+++ b/tests/gold_tests/pluginTest/slice/slice_error.test.py
@@ -34,7 +34,7 @@
 server = Test.MakeOriginServer("server", lookup_key="{%Range}{PATH}")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_server", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 
 body = "the quick brown fox"  # len 19
 
diff --git a/tests/gold_tests/pluginTest/slice/slice_prefetch.test.py b/tests/gold_tests/pluginTest/slice/slice_prefetch.test.py
index 59b0286..68d9ebc 100644
--- a/tests/gold_tests/pluginTest/slice/slice_prefetch.test.py
+++ b/tests/gold_tests/pluginTest/slice/slice_prefetch.test.py
@@ -36,7 +36,7 @@
 server = Test.MakeOriginServer("server", lookup_key="{%Range}")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 block_bytes_1 = 7
 block_bytes_2 = 5
@@ -101,7 +101,7 @@
     ' @plugin=cache_range_requests.so',
 ])
 
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
 ts.Disk.logging_yaml.AddLines([
     'logging:',
     '  formats:',
diff --git a/tests/gold_tests/pluginTest/slice/slice_regex.test.py b/tests/gold_tests/pluginTest/slice/slice_regex.test.py
index 8fc8003..cdcf0de 100644
--- a/tests/gold_tests/pluginTest/slice/slice_regex.test.py
+++ b/tests/gold_tests/pluginTest/slice/slice_regex.test.py
@@ -34,7 +34,7 @@
 server = Test.MakeOriginServer("server")
 
 # Define ATS and configure.
-ts = Test.MakeATSProcess("ts", command="traffic_server", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 
 # default root
 request_header_chk = {"headers":
diff --git a/tests/gold_tests/pluginTest/slice/slice_selfhealing.test.py b/tests/gold_tests/pluginTest/slice/slice_selfhealing.test.py
index 2a3af17..a03f108 100644
--- a/tests/gold_tests/pluginTest/slice/slice_selfhealing.test.py
+++ b/tests/gold_tests/pluginTest/slice/slice_selfhealing.test.py
@@ -47,7 +47,7 @@
 server = Test.MakeOriginServer("server", lookup_key="{%uuid}")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 
 # default root
 req_header_chk = {"headers":
@@ -82,7 +82,7 @@
     '  @plugin=cache_range_requests.so @pparam=--ims-header=crr-foo',
 ])
 
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
 
 ts.Disk.records_config.update({
     'proxy.config.diags.debug.enabled': 0,
diff --git a/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py b/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py
index 005c0e4..6bec787 100644
--- a/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py
+++ b/tests/gold_tests/pluginTest/stats_over_http/stats_over_http.test.py
@@ -46,6 +46,8 @@
 
         self.ts.Disk.records_config.update({
             "proxy.config.http.server_ports": f"{self.ts.Variables.port}",
+            "proxy.config.diags.debug.enabled": 1,
+            "proxy.config.diags.debug.tags": "stats_over_http"
         })
 
     def __checkProcessBefore(self, tr):
diff --git a/tests/gold_tests/pluginTest/test_hooks/200.gold b/tests/gold_tests/pluginTest/test_hooks/200.gold
new file mode 100644
index 0000000..a887573
--- /dev/null
+++ b/tests/gold_tests/pluginTest/test_hooks/200.gold
@@ -0,0 +1,28 @@
+``
+> POST /contentlength HTTP/1.1``
+> Host:``
+> User-Agent: curl/``
+> Accept: */*``
+> Content-Length: 22``
+> Content-Type: application/``
+``
+< HTTP/1.1 200 OK``
+< Server: ATS/``
+< Content-Length: 23``
+< Date:``
+< Age:``
+``
+> POST /chunked``
+> Host:``
+> User-Agent: curl/``
+> Accept: */*``
+> Transfer-Encoding: chunked``
+> Content-Type: application/``
+``
+< HTTP/1.1 200 OK``
+< Server: ATS/``
+< Date: ``
+< Age: 0``
+< Transfer-Encoding: chunked``
+< Connection: keep-alive``
+``
diff --git a/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
new file mode 100644
index 0000000..28d1ef2
--- /dev/null
+++ b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
@@ -0,0 +1,121 @@
+'''
+Verify HTTP body buffering.
+'''
+#  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.
+
+import os
+
+
+def int_to_hex_string(int_value):
+    '''
+    Convert the given int value to a hex string with no '0x' prefix.
+
+    >>> int_to_hex_string(0)
+    '0'
+    >>> int_to_hex_string(1)
+    '1'
+    >>> int_to_hex_string(10)
+    'a'
+    >>> int_to_hex_string(16)
+    '10'
+    >>> int_to_hex_string(17)
+    'f1'
+    '''
+    if not isinstance(int_value, int):
+        raise ValueError("Input should be an int type.")
+    return hex(int_value).split('x')[1]
+
+
+class BodyBufferTest:
+    def __init__(cls, description):
+        Test.Summary = description
+        cls._origin_max_connections = 3
+        cls.setupOriginServer()
+        cls.setupTS()
+
+    def setupOriginServer(self):
+        self._server = Test.MakeOriginServer("server")
+        self.content_length_request_body = "content-length request"
+        self.content_length_size = len(self.content_length_request_body)
+        request_header = {"headers": "POST /contentlength HTTP/1.1\r\n"
+                          "Host: www.example.com\r\n"
+                          f"Content-Length: {self.content_length_size}\r\n\r\n",
+                          "timestamp": "1469733493.993",
+                          "body": self.content_length_request_body}
+        content_length_response_body = "content-length response"
+        content_length_response_size = len(content_length_response_body)
+        response_header = {"headers": "HTTP/1.1 200 OK\r\n"
+                           "Server: microserver\r\n"
+                           f"Content-Length: {content_length_response_size}\r\n\r\n"
+                           "Connection: close\r\n\r\n",
+                           "timestamp": "1469733493.993",
+                           "body": content_length_response_body}
+        self._server.addResponse("sessionlog.json", request_header, response_header)
+
+        self.chunked_request_body = "chunked request"
+        hex_size = int_to_hex_string(len(self.chunked_request_body))
+        self.encoded_chunked_request = f"{hex_size}\r\n{self.chunked_request_body}\r\n0\r\n\r\n"
+        self.encoded_chunked_size = len(self.content_length_request_body)
+        request_header2 = {"headers": "POST /chunked HTTP/1.1\r\n"
+                           "Transfer-Encoding: chunked\r\n"
+                           "Host: www.example.com\r\n"
+                           "Connection: keep-alive\r\n\r\n",
+                           "timestamp": "1469733493.993",
+                           "body": self.encoded_chunked_request}
+        self.chunked_response_body = "chunked response"
+        hex_size = int_to_hex_string(len(self.chunked_response_body))
+        self.encoded_chunked_response = f"{hex_size}\r\n{self.chunked_response_body}\r\n0\r\n\r\n"
+        response_header2 = {"headers": "HTTP/1.1 200 OK\r\n"
+                            "Transfer-Encoding: chunked\r\n"
+                            "Server: microserver\r\n"
+                            "Connection: close\r\n\r\n",
+                            "timestamp": "1469733493.993",
+                            "body": self.encoded_chunked_response}
+        self._server.addResponse("sessionlog.json", request_header2, response_header2)
+
+    def setupTS(self):
+        self._ts = Test.MakeATSProcess("ts", select_ports=False)
+        self._ts.Disk.remap_config.AddLine(
+            f'map / http://127.0.0.1:{self._server.Variables.Port}'
+        )
+        Test.PrepareInstalledPlugin('request_buffer.so', self._ts)
+        self._ts.Disk.records_config.update({
+            'proxy.config.diags.debug.enabled': 1,
+            'proxy.config.diags.debug.tags': 'request_buffer',
+        })
+
+        self._ts.Disk.traffic_out.Content = Testers.ContainsExpression(
+            rf"request_buffer_plugin gets the request body with length\[{self.content_length_size}\]",
+            "Verify that the plugin parsed the content-length request body data.")
+        self._ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            rf"request_buffer_plugin gets the request body with length\[{self.encoded_chunked_size}\]",
+            "Verify that the plugin parsed the chunked request body.")
+
+    def run(self):
+        tr = Test.AddTestRun()
+        # Send both a Content-Length request and a chunked-encoded request.
+        tr.Processes.Default.Command = (
+            f'curl -v http://127.0.0.1:{self._ts.Variables.port}/contentlength -d "{self.content_length_request_body}" --next '
+            f'-v http://127.0.0.1:{self._ts.Variables.port}/chunked -H "Transfer-Encoding: chunked" -d "{self.chunked_request_body}"')
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.StartBefore(self._server)
+        tr.Processes.Default.StartBefore(Test.Processes.ts)
+        tr.Processes.Default.Streams.stderr = "200.gold"
+
+
+bodyBufferTest = BodyBufferTest("Test request body buffering.")
+bodyBufferTest.run()
diff --git a/tests/gold_tests/pluginTest/test_hooks/hook_add.test.py b/tests/gold_tests/pluginTest/test_hooks/hook_add.test.py
index 5185ca5..d908c8e 100644
--- a/tests/gold_tests/pluginTest/test_hooks/hook_add.test.py
+++ b/tests/gold_tests/pluginTest/test_hooks/hook_add.test.py
@@ -30,7 +30,7 @@
 response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
 server.addResponse("sessionlog.json", request_header, response_header)
 
-ts = Test.MakeATSProcess("ts", enable_tls=False, enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 
 ts.Disk.records_config.update({
     'proxy.config.diags.debug.tags': 'test',
diff --git a/tests/gold_tests/pluginTest/test_hooks/ssn_start_delay_hook.test.py b/tests/gold_tests/pluginTest/test_hooks/ssn_start_delay_hook.test.py
index b4da800..d0aa6b6 100644
--- a/tests/gold_tests/pluginTest/test_hooks/ssn_start_delay_hook.test.py
+++ b/tests/gold_tests/pluginTest/test_hooks/ssn_start_delay_hook.test.py
@@ -30,7 +30,7 @@
 response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
 server.addResponse("sessionlog.json", request_header, response_header)
 
-ts = Test.MakeATSProcess("ts", enable_tls=False)
+ts = Test.MakeATSProcess("ts")
 
 ts.Disk.records_config.update({
     'proxy.config.diags.debug.tags': 'test',
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
index 5c1aa6b..d6c996b 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
@@ -38,7 +38,7 @@
 
 
 # Define ATS and configure it.
-ts = Test.MakeATSProcess("ts", command='traffic_manager', enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 replay_dir = os.path.join(ts.RunDirectory, "ts", "log")
 
 ts.addSSLfile("ssl/server.pem")
diff --git a/tests/gold_tests/pluginTest/transform/transaction-with-body.replays.yaml b/tests/gold_tests/pluginTest/transform/transaction-with-body.replays.yaml
index 08153fd..07a4b1a 100644
--- a/tests/gold_tests/pluginTest/transform/transaction-with-body.replays.yaml
+++ b/tests/gold_tests/pluginTest/transform/transaction-with-body.replays.yaml
@@ -1,36 +1,227 @@
+#  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.
+
 meta:
   version: "1.0"
 
 sessions:
 - transactions:
+
+  #---------------------------------------------------------------------------
+  # Verify that Content-Length response bodies can be consumed.
+  #---------------------------------------------------------------------------
   - client-request:
-      method: "GET"
+      method: "POST"
       version: "1.1"
-      url: "/test/http11"
+      url: "/test/http11_response"
       headers:
         fields:
         - [ Host, example.com ]
-        - [ uuid, 1 ]
-        - [ Content-Length, 20 ]
-        # The example plugin only prints bodies with responses to requests
-        # containing The TS-Agent header.
-        - [ TS-Agent, 1 ]
+        - [ uuid, 10 ]
+        - [ X-Dump-Response, 1 ]
+        - [ Content-Length, 31 ]
       content:
-        data: "http1.1_request_body"
+        data: "http1.1_request_body_not_dumped"
 
     proxy-request:
       headers:
         fields:
-        - [ TS-Agent, { as: present } ]
+        - [ X-Dump-Response, { as: present } ]
 
     server-response:
       status: 200
       reason: OK
       headers:
         fields:
-        - [ Content-Length, 21 ]
+        - [ Content-Length, 31 ]
       content:
-        data: "http1.1_response_body"
+        data: "http1.1_cl_response_body_dumped"
+
+    proxy-response:
+      status: 200
+
+  #---------------------------------------------------------------------------
+  # Verify that chunked response bodies can be consumed.
+  #---------------------------------------------------------------------------
+  - client-request:
+      method: "POST"
+      version: "1.1"
+      url: "/test/http11_response"
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, 11 ]
+        - [ X-Dump-Response, 1 ]
+        - [ Content-Length, 31 ]
+      content:
+        data: "http1.1_request_body_not_dumped"
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Dump-Response, { as: present } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Transfer-Encoding, chunked ]
+      content:
+        data: "http1.1_chunked_response_body_dumped"
+
+    proxy-response:
+      status: 200
+
+  #---------------------------------------------------------------------------
+  # Verify that Content-Length request bodies can be consumed.
+  #---------------------------------------------------------------------------
+  - client-request:
+      method: "POST"
+      version: "1.1"
+      url: "/test/http11_request"
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, 12 ]
+        # The example plugin only prints bodies with responses to requests
+        # containing The TS-Agent header.
+        - [ X-Dump-Request, 1 ]
+        - [ Content-Length, 30 ]
+      content:
+        data: "http1.1_cl_request_body_dumped"
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Dump-Request, { as: present } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Transfer-Encoding, chunked ]
+      content:
+        data: "http1.1_response_body_not_dumped"
+
+    proxy-response:
+      status: 200
+
+  #---------------------------------------------------------------------------
+  # Verify that chunked request bodies can be consumed.
+  #---------------------------------------------------------------------------
+  - client-request:
+      method: "POST"
+      version: "1.1"
+      url: "/test/http11_request"
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, 13 ]
+        # The example plugin only prints bodies with responses to requests
+        # containing The TS-Agent header.
+        - [ X-Dump-Request, 1 ]
+        - [ Transfer-Encoding, chunked ]
+      content:
+        data: "http1.1_chunked_request_body_dumped"
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Dump-Request, { as: present } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Transfer-Encoding, chunked ]
+      content:
+        data: "http1.1_response_body_not_dumped"
+
+    proxy-response:
+      status: 200
+
+- protocol:
+  - name: http
+    version: 2
+  - name: tls
+    sni: test_sni
+  - name: tcp
+  - name: ip
+
+  transactions:
+
+  #---------------------------------------------------------------------------
+  # Verify that HTTP/2 response bodies can be consumed.
+  #---------------------------------------------------------------------------
+  - client-request:
+      headers:
+        fields:
+        - [ :method, POST ]
+        - [ :scheme, https ]
+        - [ :authority, www.example.com ]
+        - [ :path, /test/http2_response ]
+        - [ uuid, 20 ]
+        - [ X-Dump-Response, 1 ]
+      content:
+        data: "http2_request_body_not_dumped"
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Dump-Response, { as: present } ]
+
+    server-response:
+      headers:
+        fields:
+        - [ :status, 200 ]
+      content:
+        data: "http2_response_body_dumped"
+
+    proxy-response:
+      status: 200
+
+  #---------------------------------------------------------------------------
+  # Verify that HTTP/2 request bodies can be consumed.
+  #---------------------------------------------------------------------------
+  - client-request:
+      headers:
+        fields:
+        - [ :method, POST ]
+        - [ :scheme, https ]
+        - [ :authority, www.example.com ]
+        - [ :path, /test/http2_response ]
+        - [ uuid, 21 ]
+        - [ X-Dump-Request, 1 ]
+      content:
+        data: "http2_request_body_dumped"
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Dump-Request, { as: present } ]
+
+    server-response:
+      headers:
+        fields:
+        - [ :status, 200 ]
+      content:
+        data: "http2_response_body_not_dumped"
 
     proxy-response:
       status: 200
diff --git a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
index b31cef5..f561f13 100644
--- a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
+++ b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
@@ -16,8 +16,6 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import os
-
 Test.Summary = '''
 Verify transaction data sink.
 '''
@@ -26,29 +24,81 @@
     Condition.PluginExists('txn_data_sink.so'),
 )
 
-replay_file = "transaction-with-body.replays.yaml"
-server = Test.MakeVerifierServerProcess("server", replay_file)
-nameserver = Test.MakeDNServer("dns", default='127.0.0.1')
 
-ts = Test.MakeATSProcess("ts", enable_cache=False)
-ts.Disk.records_config.update({
-    'proxy.config.diags.debug.enabled': 1,
-    'proxy.config.diags.debug.tags': 'txn_data_sink',
-    'proxy.config.dns.nameservers': f"127.0.0.1:{nameserver.Variables.Port}",
-})
-ts.Disk.remap_config.AddLine(
-    f'map / http://localhost:{server.Variables.http_port}/'
-)
-ts.Disk.plugin_config.AddLine('txn_data_sink.so')
+class TransactionDataSyncTest:
 
-# Verify that the various aspects of the expected debug output for the
-# transaction are logged.
-ts.Disk.traffic_out.Content = Testers.ContainsExpression(
-    '"http1.1_response_body"',
-    "The response body should be printed by the plugin.")
+    replay_file = "transaction-with-body.replays.yaml"
 
-tr = Test.AddTestRun()
-tr.Processes.Default.StartBefore(server)
-tr.Processes.Default.StartBefore(ts)
-tr.Processes.Default.StartBefore(nameserver)
-tr.AddVerifierClientProcess("client-1", replay_file, http_ports=[ts.Variables.port])
+    def __init__(self):
+        self._setupOriginServer()
+        self._setupNameserver()
+        self._setupTS()
+
+    def _setupOriginServer(self):
+        self.server = Test.MakeVerifierServerProcess(
+            "server", self.replay_file)
+
+    def _setupNameserver(self):
+        self.nameserver = Test.MakeDNServer("dns", default='127.0.0.1')
+
+    def _setupTS(self):
+        self.ts = Test.MakeATSProcess("ts", enable_cache=False, enable_tls=True)
+        self.ts.Disk.records_config.update({
+            "proxy.config.ssl.server.cert.path": f'{self.ts.Variables.SSLDir}',
+            "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}',
+            "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE',
+            "proxy.config.dns.nameservers": f"127.0.0.1:{self.nameserver.Variables.Port}",
+
+            'proxy.config.diags.debug.enabled': 1,
+            'proxy.config.diags.debug.tags': 'http|txn_data_sink',
+        })
+        self.ts.addDefaultSSLFiles()
+        self.ts.Disk.remap_config.AddLine(
+            f'map / http://localhost:{self.server.Variables.http_port}/'
+        )
+        self.ts.Disk.ssl_multicert_config.AddLine(
+            'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+        )
+        self.ts.Disk.plugin_config.AddLine('txn_data_sink.so')
+
+        # All of the bodies that contained "not_dumped" were not configured to
+        # be dumped. Therefore it is a bug if they show up in the logs.
+        self.ts.Disk.traffic_out.Content += Testers.ExcludesExpression(
+            'body_not_dumped',
+            "An unexpected body was dumped.")
+
+        # Verify that each of the configured transaction bodies were dumped.
+        self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            'http1.1_cl_response_body_dumped',
+            "The expected HTTP/1.1 Content-Length response body was dumped.")
+        self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            'http1.1_chunked_response_body_dumped',
+            "The expected HTTP/1.1 chunked response body was dumped.")
+        self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            'http1.1_cl_request_body_dumped',
+            "The expected HTTP/1.1 Content-Length request body was dumped.")
+        self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            'http1.1_chunked_request_body_dumped',
+            "The expected HTTP/1.1 chunked request body was dumped.")
+        self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            '"http2_response_body_dumped"',
+            "The expected HTTP/2 response body was dumped.")
+        self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
+            'http2_request_body_dumped',
+            "The expected HTTP/2 request body was dumped.")
+
+    def run(self):
+        """Configure a TestRun for the test."""
+        tr = Test.AddTestRun()
+        tr.Processes.Default.StartBefore(self.server)
+        tr.Processes.Default.StartBefore(self.nameserver)
+        tr.Processes.Default.StartBefore(self.ts)
+        tr.AddVerifierClientProcess(
+            "client",
+            self.replay_file,
+            http_ports=[self.ts.Variables.port],
+            https_ports=[self.ts.Variables.ssl_port],
+            other_args='--thread-limit 1')
+
+
+TransactionDataSyncTest().run()
diff --git a/tests/gold_tests/pluginTest/xdebug/x_cache_info/x_cache_info.test.py b/tests/gold_tests/pluginTest/xdebug/x_cache_info/x_cache_info.test.py
index 5d6529b..c9e2518 100644
--- a/tests/gold_tests/pluginTest/xdebug/x_cache_info/x_cache_info.test.py
+++ b/tests/gold_tests/pluginTest/xdebug/x_cache_info/x_cache_info.test.py
@@ -36,7 +36,7 @@
     'proxy.config.diags.debug.tags': 'http'
 })
 
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache-info')
 
 ts.Disk.remap_config.AddLine(
     "map http://one http://127.0.0.1:{0}".format(server.Variables.Port)
diff --git a/tests/gold_tests/pluginTest/xdebug/x_effective_url/x_effective_url.test.py b/tests/gold_tests/pluginTest/xdebug/x_effective_url/x_effective_url.test.py
index 29c09c5..32bd344 100644
--- a/tests/gold_tests/pluginTest/xdebug/x_effective_url/x_effective_url.test.py
+++ b/tests/gold_tests/pluginTest/xdebug/x_effective_url/x_effective_url.test.py
@@ -39,7 +39,7 @@
     'proxy.config.diags.debug.enabled': 0,
 })
 
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-effective-url')
 
 ts.Disk.remap_config.AddLine(
     "map http://one http://127.0.0.1:{0}".format(server.Variables.Port)
diff --git a/tests/gold_tests/pluginTest/xdebug/x_remap/x_remap.test.py b/tests/gold_tests/pluginTest/xdebug/x_remap/x_remap.test.py
index a4dcee0..8704e63 100644
--- a/tests/gold_tests/pluginTest/xdebug/x_remap/x_remap.test.py
+++ b/tests/gold_tests/pluginTest/xdebug/x_remap/x_remap.test.py
@@ -37,7 +37,7 @@
     # 'proxy.config.diags.debug.tags': 'xdebug'
 })
 
-ts.Disk.plugin_config.AddLine('xdebug.so')
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-remap,probe')
 
 ts.Disk.remap_config.AddLine(
     "map http://one http://127.0.0.1:{0}".format(server.Variables.Port)
diff --git a/tests/gold_tests/redirect/redirect.test.py b/tests/gold_tests/redirect/redirect.test.py
index 01777dc..3074ab7 100644
--- a/tests/gold_tests/redirect/redirect.test.py
+++ b/tests/gold_tests/redirect/redirect.test.py
@@ -46,7 +46,7 @@
 logging:
   formats:
     - name: custom
-      format: "client_url=%<cqu> cache_result: code=%<crc> subcode=%<crsc>"
+      format: "client_url=%<pqu> cache_result: code=%<crc> subcode=%<crsc>"
   logs:
     - filename: the_log
       format: custom
diff --git a/tests/gold_tests/remap/conf_remap_float.test.py b/tests/gold_tests/remap/conf_remap_float.test.py
index 7095d10..4bb67f2 100644
--- a/tests/gold_tests/remap/conf_remap_float.test.py
+++ b/tests/gold_tests/remap/conf_remap_float.test.py
@@ -20,7 +20,7 @@
 '''
 Test.testName = 'Float in conf_remap Config Test'
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 
 ts.Disk.MakeConfigFile('conf_remap.config').AddLines([
     'CONFIG proxy.config.http.background_fill_completed_threshold FLOAT 0.500000'
@@ -36,6 +36,6 @@
 tr.StillRunningAfter = ts
 
 p = tr.Processes.Default
-p.Command = "traffic_ctl config describe proxy.config.http.background_fill_completed_threshold"
+p.Command = f"traffic_ctl config describe proxy.config.http.background_fill_completed_threshold"
 p.ReturnCode = 0
 p.StartBefore(Test.Processes.ts)
diff --git a/tests/gold_tests/remap/remap_reload.test.py b/tests/gold_tests/remap/remap_reload.test.py
index 2f9e085..61f22c2 100644
--- a/tests/gold_tests/remap/remap_reload.test.py
+++ b/tests/gold_tests/remap/remap_reload.test.py
@@ -25,7 +25,7 @@
 replay_file_3 = "reload_3.replay.yaml"
 replay_file_4 = "reload_4.replay.yaml"
 
-tm = Test.MakeATSProcess("tm", command="traffic_manager")
+tm = Test.MakeATSProcess("ts")
 tm.Disk.diags_log.Content = Testers.ContainsExpression("remap.config failed to load", "Remap should fail to load")
 remap_cfg_path = os.path.join(tm.Variables.CONFIGDIR, 'remap.config')
 
diff --git a/tests/gold_tests/runroot/runroot_manager.test.py b/tests/gold_tests/runroot/runroot_manager.test.py
deleted file mode 100644
index a6294e9..0000000
--- a/tests/gold_tests/runroot/runroot_manager.test.py
+++ /dev/null
@@ -1,54 +0,0 @@
-'''
-'''
-#  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.
-
-import os
-
-Test.Summary = '''
-Test for using of runroot of traffic_manager.
-'''
-Test.ContinueOnFail = False
-
-# create runroot for testing
-runroot_path = os.path.join(Test.RunDirectory, "runroot")
-rr_file = os.path.join(Test.RunDirectory, "rr_tmp")
-
-tr = Test.AddTestRun("create runroot and deal with it")
-tr.Processes.Default.Command = "$ATS_BIN/traffic_layout init --path " + runroot_path + " --absolute; " + \
-    "mkdir " + rr_file + "; mv " + \
-    os.path.join(runroot_path, "runroot.yaml") + " " + \
-    os.path.join(rr_file, "runroot.yaml")
-f = tr.Disk.File(os.path.join(rr_file, "runroot.yaml"))
-f.Exists = True
-
-
-def StopProcess(event, time):
-    if event.TotalRunTime > time:
-        event.object.Stop()
-    return 0, "stop manager process", "manager will be killed"
-
-
-tr = Test.AddTestRun("manager runroot test")
-
-trafficserver_dir = os.path.join(runroot_path, 'var', 'trafficserver')
-tr.ChownForATSProcess(trafficserver_dir)
-
-p = tr.Processes.Default
-p.Command = "$ATS_BIN/traffic_manager --run-root=" + rr_file
-p.RunningEvent.Connect(Testers.Lambda(lambda ev: StopProcess(ev, 10)))
-p.Streams.All = Testers.ContainsExpression("traffic_server: using root directory '" +
-                                           runroot_path + "'", "check if the right runroot is passed down")
diff --git a/tests/gold_tests/timeout/active_timeout.test.py b/tests/gold_tests/timeout/active_timeout.test.py
index d487e14..8c8b8bc 100644
--- a/tests/gold_tests/timeout/active_timeout.test.py
+++ b/tests/gold_tests/timeout/active_timeout.test.py
@@ -64,10 +64,7 @@
 tr3.Processes.Default.Command = 'curl -k -i --http2 https://127.0.0.1:{0}/file'.format(ts.Variables.ssl_port)
 tr3.Processes.Default.Streams.stdout = Testers.ContainsExpression("Activity Timeout", "Request should fail with active timeout")
 
-# Commenting out the HTTP/3 test since 9.x and before does not support the
-# latest version of HTTP/3 which is used by curl. ATS 10.x does support this
-# later version, so this test is run for that release and later.
-# if Condition.HasATSFeature('TS_USE_QUIC') and Condition.HasCurlFeature('http3'):
-#     tr4 = Test.AddTestRun("tr")
-#     tr4.Processes.Default.Command = 'curl -k -i --http3 https://127.0.0.1:{0}/file'.format(ts.Variables.ssl_port)
-#     tr4.Processes.Default.Streams.stdout = Testers.ContainsExpression("Activity Timeout", "Request should fail with active timeout")
+if Condition.HasATSFeature('TS_USE_QUIC') and Condition.HasCurlFeature('http3'):
+    tr4 = Test.AddTestRun("tr")
+    tr4.Processes.Default.Command = 'curl -k -i --http3 https://127.0.0.1:{0}/file'.format(ts.Variables.ssl_port)
+    tr4.Processes.Default.Streams.stdout = Testers.ContainsExpression("Activity Timeout", "Request should fail with active timeout")
diff --git a/tests/gold_tests/timeout/conn_timeout.test.py b/tests/gold_tests/timeout/conn_timeout.test.py
index 7eb34cf..0a5b64e 100644
--- a/tests/gold_tests/timeout/conn_timeout.test.py
+++ b/tests/gold_tests/timeout/conn_timeout.test.py
@@ -47,7 +47,7 @@
 logging:
   formats:
     - name: testformat
-      format: '%<pssc> %<cquc> %<pscert> %<cscert>'
+      format: '%<pssc> %<pquc> %<pscert> %<cscert>'
   logs:
     - mode: ascii
       format: testformat
diff --git a/tests/gold_tests/timeout/tls_conn_timeout.test.py b/tests/gold_tests/timeout/tls_conn_timeout.test.py
index 5a635b1..86da7ec 100644
--- a/tests/gold_tests/timeout/tls_conn_timeout.test.py
+++ b/tests/gold_tests/timeout/tls_conn_timeout.test.py
@@ -43,7 +43,6 @@
 ts.Disk.records_config.update({
     'proxy.config.url_remap.remap_required': 1,
     'proxy.config.http.connect_attempts_timeout': 1,
-    'proxy.config.http.post_connect_attempts_timeout': 1,
     'proxy.config.http.connect_attempts_max_retries': 1,
     'proxy.config.http.transaction_no_activity_timeout_out': 4,
     'proxy.config.diags.debug.enabled': 0,
diff --git a/tests/gold_tests/tls/ssl_multicert_loader.test.py b/tests/gold_tests/tls/ssl_multicert_loader.test.py
index 53cb1e7..fd5d258 100644
--- a/tests/gold_tests/tls/ssl_multicert_loader.test.py
+++ b/tests/gold_tests/tls/ssl_multicert_loader.test.py
@@ -20,7 +20,7 @@
 
 sni_domain = 'example.com'
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server3")
 request_header = {"headers": f"GET / HTTP/1.1\r\nHost: {sni_domain}\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
@@ -96,7 +96,7 @@
 # Also, not explicitly setting proxy.config.ssl.server.multicert.exit_on_load_fail
 # to catch if the current default (1) changes in the future
 
-ts2 = Test.MakeATSProcess("ts2", command="traffic_manager", enable_tls=True)
+ts2 = Test.MakeATSProcess("ts2", enable_tls=True)
 ts2.Disk.ssl_multicert_config.AddLines([
     'dest_ip=* ssl_cert_name=server.pem_doesnotexist ssl_key_name=server.key',
 ])
@@ -106,7 +106,7 @@
 tr4.Processes.Default.ReturnCode = 0
 tr4.Processes.Default.StartBefore(ts2)
 
-ts2.ReturnCode = 2
+ts2.ReturnCode = 70  # ink_fatal will exit with EX_SOFTWARE.
 ts2.Ready = 0  # Need this to be 0 because we are testing shutdown, this is to make autest not think ats went away for a bad reason.
 ts2.Disk.traffic_out.Content = Testers.ExcludesExpression(
     'Traffic Server is fully initialized',
diff --git a/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py b/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py
index 1a79859..4f11e8f 100644
--- a/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py
+++ b/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py
@@ -23,7 +23,7 @@
 '''
 
 # Define default ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 server = Test.MakeOriginServer("server", ssl=True)
 dns = Test.MakeDNServer("dns")
 
diff --git a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
index edd18e7..9b6a720 100644
--- a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
+++ b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
@@ -21,7 +21,7 @@
 '''
 
 # Define default ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 server = Test.MakeOriginServer("server", ssl=True)
 server3 = Test.MakeOriginServer("server3", ssl=True)
 
diff --git a/tests/gold_tests/tls/tls_client_alpn_configuration.replay.yaml b/tests/gold_tests/tls/tls_client_alpn_configuration.replay.yaml
new file mode 100644
index 0000000..9ebb7ad
--- /dev/null
+++ b/tests/gold_tests/tls/tls_client_alpn_configuration.replay.yaml
@@ -0,0 +1,112 @@
+#  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.
+
+#
+# Verify negative_revalidating disabled behavior. This replay file assumes:
+#   * ATS is configured with negative_revalidating disabled.
+#   * max_stale_age is set to 6 seconds.
+#
+
+meta:
+  version: "1.0"
+
+sessions:
+
+# HTTP/1.1 over TLS.
+- protocol:
+  - name: tls
+    sni: www.example.com
+  - name: tcp
+  - name: ip
+
+  transactions:
+
+  # This test has more to do with ALPN configuration than the transactions. The
+  # following generates a simple request and response.
+  - client-request:
+      method: GET
+      url: /some/path/2
+      version: '1.1'
+      headers:
+        fields:
+        - [ Host, www.example.com ]
+        - [ Content-Length, 0 ]
+        - [ X-Request, alpn_request ]
+        - [ uuid, first-request ]
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Request, {value: 'alpn_request', as: equal } ]
+
+    server-response:
+        status: 200
+        reason: OK
+        headers:
+          fields:
+          - [ Date, "Sat, 16 Mar 2019 03:11:36 GMT" ]
+          - [ Content-Length, 36 ]
+          - [ Connection, keep-alive ]
+          - [ X-Response, alpn_response ]
+
+    proxy-response:
+      headers:
+        fields:
+        - [ X-Response, {value: 'alpn_response', as: equal } ]
+
+# HTTP/2 over TLS.
+- protocol:
+  - name: http
+    version: 2
+  - name: tls
+    sni: www.example.com
+  - name: tcp
+  - name: ip
+
+  transactions:
+
+  # This test has more to do with ALPN configuration than the transactions. The
+  # following generates a simple request and response.
+  - client-request:
+      method: GET
+      url: /some/path/2
+      version: '1.1'
+      headers:
+        fields:
+        - [ Host, www.example.com ]
+        - [ Content-Length, 0 ]
+        - [ X-Request, alpn_request ]
+        - [ uuid, first-request ]
+
+    proxy-request:
+      headers:
+        fields:
+        - [ X-Request, {value: 'alpn_request', as: equal } ]
+
+    server-response:
+        status: 200
+        reason: OK
+        headers:
+          fields:
+          - [ Date, "Sat, 16 Mar 2019 03:11:36 GMT" ]
+          - [ Content-Length, 36 ]
+          - [ Connection, keep-alive ]
+          - [ X-Response, alpn_response ]
+
+    proxy-response:
+      headers:
+        fields:
+        - [ X-Response, {value: 'alpn_response', as equal } ]
diff --git a/tests/gold_tests/tls/tls_client_alpn_configuration.test.py b/tests/gold_tests/tls/tls_client_alpn_configuration.test.py
new file mode 100644
index 0000000..1e4fac5
--- /dev/null
+++ b/tests/gold_tests/tls/tls_client_alpn_configuration.test.py
@@ -0,0 +1,183 @@
+"""Verify ALPN to origin functionality."""
+
+#  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.
+
+from typing import Optional
+
+
+Test.Summary = __doc__
+
+
+class TestAlpnFunctionality:
+    """Define an object to test a set of ALPN functionality."""
+
+    _replay_file: str = 'tls_client_alpn_configuration.replay.yaml'
+    _server_counter: int = 0
+    _ts_counter: int = 0
+    _client_counter: int = 0
+
+    def __init__(
+            self,
+            records_config_alpn: Optional[str] = None,
+            conf_remap_alpn: Optional[str] = None,
+            alpn_is_malformed: bool = False):
+        """Declare the various test Processes.
+
+        :param records_config_alpn: The string with which to configure the ATS
+        ALPN via proxy.config.ssl.client.alpn_protocols in the records.config.
+        If the paramenter is None, then no ALPN configuration will be
+        explicitly set and ATS will use the default value.
+
+        :param conf_remap_alpn: The string with which to configure the Traffic
+        Server ALPN proxy.config.http.alpn_protocols configuration via
+        conf_remap. If the parameter is None, then no conf_remap configuration
+        will be set.
+
+        :param alpn_is_malformed: If True, then the configured ALPN string in
+        the records.config will be malformed. The TestRun will be configured to
+        expect a warning and the server will be configured to receive no ALPN.
+        """
+        self._alpn = records_config_alpn
+        self._alpn_conf_remap_alpn = conf_remap_alpn
+        self._alpn_is_malformed = alpn_is_malformed
+
+        configured_alpn = records_config_alpn if conf_remap_alpn is None else conf_remap_alpn
+        if alpn_is_malformed:
+            configured_alpn = None
+        self._server = self._configure_server(configured_alpn)
+
+        self._ts = self._configure_trafficserver(
+            records_config_alpn,
+            conf_remap_alpn,
+            alpn_is_malformed)
+
+    def _configure_server(self, expected_alpn: Optional[str] = None):
+        """Configure the test server.
+
+        :param expected_alpn: The ALPN expected from the client. If this is
+        None, then the server will not expect an ALPN value.
+        """
+        server = Test.MakeVerifierServerProcess(
+            f'server-{TestAlpnFunctionality._server_counter}',
+            self._replay_file)
+        TestAlpnFunctionality._server_counter += 1
+
+        if expected_alpn is None:
+            server.Streams.stdout = Testers.ContainsExpression(
+                'Negotiated ALPN: none',
+                'Verify that ATS sent no ALPN string.')
+        else:
+            protocols = expected_alpn.split(',')
+            for protocol in protocols:
+                server.Streams.stdout = Testers.ContainsExpression(
+                    f'ALPN.*:.*{protocol}',
+                    'Verify that the server parsed the configured ALPN string from ATS.')
+        return server
+
+    def _configure_trafficserver(
+            self,
+            records_config_alpn: Optional[str] = None,
+            conf_remap_alpn: Optional[str] = None,
+            alpn_is_malformed: bool = False):
+        """Configure a Traffic Server process.
+
+        :param records_config_alpn: See the description of this parameter in
+        TestAlpnFunctionality._init__.
+        """
+        ts = Test.MakeATSProcess(
+            f'ts-{TestAlpnFunctionality._ts_counter}',
+            enable_tls=True,
+            enable_cache=False)
+        TestAlpnFunctionality._ts_counter += 1
+
+        ts.addDefaultSSLFiles()
+        ts.Disk.records_config.update({
+            "proxy.config.ssl.server.cert.path": f'{ts.Variables.SSLDir}',
+            "proxy.config.ssl.server.private_key.path": f'{ts.Variables.SSLDir}',
+            "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE',
+
+            'proxy.config.diags.debug.enabled': 3,
+            'proxy.config.diags.debug.tags': 'ssl',
+        })
+
+        if records_config_alpn is not None:
+            ts.Disk.records_config.update({
+                'proxy.config.ssl.client.alpn_protocols': records_config_alpn,
+            })
+
+        ts.Disk.ssl_multicert_config.AddLine(
+            'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+        )
+
+        conf_remap_specification = ''
+        if conf_remap_alpn is not None:
+            conf_remap_specification = (
+                '@plugin=conf_remap.so '
+                f'@pparam=proxy.config.ssl.client.alpn_protocols={conf_remap_alpn}')
+
+        ts.Disk.remap_config.AddLine(
+            f'map / https://127.0.0.1:{self._server.Variables.https_port} {conf_remap_specification}'
+        )
+
+        if alpn_is_malformed:
+            ts.Disk.diags_log.Content = Testers.ContainsExpression(
+                "ERROR.*ALPN",
+                "There should be no ALPN parse warnings.")
+        else:
+            ts.Disk.diags_log.Content += Testers.ExcludesExpression(
+                "ERROR.*ALPN",
+                "There should be no ALPN parse warnings.")
+
+        return ts
+
+    def run(self):
+        """Configure the TestRun."""
+        description = "default" if self._alpn is None else self._alpn
+        tr = Test.AddTestRun(f'ATS ALPN configuration: {description}')
+        tr.Processes.Default.StartBefore(self._server)
+        tr.Processes.Default.StartBefore(self._ts)
+
+        tr.AddVerifierClientProcess(
+            f'client-{TestAlpnFunctionality._client_counter}',
+            self._replay_file,
+            https_ports=[self._ts.Variables.ssl_port])
+        TestAlpnFunctionality._client_counter += 1
+
+
+TestAlpnFunctionality().run()
+TestAlpnFunctionality(
+    records_config_alpn='http/1.1').run()
+TestAlpnFunctionality(
+    records_config_alpn='http/1.1,http/1.0').run()
+TestAlpnFunctionality(
+    records_config_alpn='http/1.1',
+    conf_remap_alpn='http/1.1,http/1.0').run()
+
+# TODO: HTTP/2 to origin comes later.
+# TestAlpnFunctionality(
+#   records_config_alpn='h2,http1.1').run()
+
+TestAlpnFunctionality(
+    records_config_alpn='not_a_protocol',
+    alpn_is_malformed=True).run()
+
+# Since we do not currently support ALPN with HTTP/2, this will be considered a
+# malformed ALPN protocol.
+# TODO: remove this when we support HTTP/2 to origin.
+TestAlpnFunctionality(
+    records_config_alpn='h2',
+    alpn_is_malformed=True).run()
diff --git a/tests/gold_tests/tls/tls_client_cert.test.py b/tests/gold_tests/tls/tls_client_cert.test.py
index a9086a6..3a66eb6 100644
--- a/tests/gold_tests/tls/tls_client_cert.test.py
+++ b/tests/gold_tests/tls/tls_client_cert.test.py
@@ -21,7 +21,7 @@
 Test different combinations of TLS handshake hooks to ensure they are applied consistently.
 '''
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 # --clientverify: "" empty string because microserver does store_true for argparse, but options is a dictionary
@@ -109,7 +109,7 @@
 logging:
   formats:
     - name: testformat
-      format: '%<pssc> %<cquc> %<pscert> %<cscert>'
+      format: '%<pssc> %<pquc> %<pscert> %<cscert>'
   logs:
     - mode: ascii
       format: testformat
@@ -117,6 +117,7 @@
 '''.split("\n")
 )
 
+
 # Should succeed
 tr = Test.AddTestRun("Connect with first client cert to first server")
 tr.Processes.Default.StartBefore(Test.Processes.ts)
diff --git a/tests/gold_tests/tls/tls_client_cert2.test.py b/tests/gold_tests/tls/tls_client_cert2.test.py
index 31b981f..c2f460d 100644
--- a/tests/gold_tests/tls/tls_client_cert2.test.py
+++ b/tests/gold_tests/tls/tls_client_cert2.test.py
@@ -21,7 +21,7 @@
 Test client certs to origin selected via wildcard names in sni
 '''
 
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 server = Test.MakeOriginServer("server",
@@ -110,7 +110,7 @@
 logging:
   formats:
     - name: testformat
-      format: '%<pssc> %<cquc> %<pscert> %<cscert>'
+      format: '%<pssc> %<pquc> %<pscert> %<cscert>'
   logs:
     - mode: ascii
       format: testformat
diff --git a/tests/gold_tests/tls/tls_client_cert2_plugin.test.py b/tests/gold_tests/tls/tls_client_cert2_plugin.test.py
index a372544..843606b 100644
--- a/tests/gold_tests/tls/tls_client_cert2_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert2_plugin.test.py
@@ -24,7 +24,7 @@
 Test offering client cert to origin, but using plugin for cert loading
 '''
 
-ts = Test.MakeATSProcess("ts", command="traffic_server")
+ts = Test.MakeATSProcess("ts")
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 server = Test.MakeOriginServer("server",
diff --git a/tests/gold_tests/tls/tls_client_cert_override.test.py b/tests/gold_tests/tls/tls_client_cert_override.test.py
index ad86f36..bf63c20 100644
--- a/tests/gold_tests/tls/tls_client_cert_override.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_override.test.py
@@ -21,7 +21,7 @@
 Test conf_remp to specify different client certificates to offer to the origin
 '''
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 server = Test.MakeOriginServer("server",
diff --git a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
index db3030a..a3f2a29 100644
--- a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
@@ -21,7 +21,8 @@
 '''
 
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
+
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 server = Test.MakeOriginServer("server",
diff --git a/tests/gold_tests/tls/tls_client_cert_plugin.test.py b/tests/gold_tests/tls/tls_client_cert_plugin.test.py
index 95af967..749b2d2 100644
--- a/tests/gold_tests/tls/tls_client_cert_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_plugin.test.py
@@ -24,7 +24,7 @@
 Test offering client cert to origin, but using plugin for cert loading
 '''
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager")
+ts = Test.MakeATSProcess("ts")
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 # --clientverify: "" empty string because microserver does store_true for argparse, but options is a dictionary
diff --git a/tests/gold_tests/tls/tls_client_verify.test.py b/tests/gold_tests/tls/tls_client_verify.test.py
index 984837f..5fc8391 100644
--- a/tests/gold_tests/tls/tls_client_verify.test.py
+++ b/tests/gold_tests/tls/tls_client_verify.test.py
@@ -22,7 +22,7 @@
 Test various options for requiring certificate from client for mutual authentication TLS
 '''
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 cafile = "{0}/signer.pem".format(Test.RunDirectory)
 cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
 server = Test.MakeOriginServer("server")
@@ -76,7 +76,7 @@
 logging:
   formats:
     - name: testformat
-      format: '%<pssc> %<cquc> %<pscert> %<cscert>'
+      format: '%<pssc> %<pquc> %<pscert> %<cscert>'
   logs:
     - mode: ascii
       format: testformat
diff --git a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
index d6d525d..4dc166b 100644
--- a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
+++ b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
@@ -21,7 +21,7 @@
 
 sni_domain = 'example.com'
 
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server3")
 request_header = {"headers": f"GET / HTTP/1.1\r\nHost: {sni_domain}\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
diff --git a/tests/gold_tests/tls/tls_tunnel.test.py b/tests/gold_tests/tls/tls_tunnel.test.py
index f35f554..3794bee 100644
--- a/tests/gold_tests/tls/tls_tunnel.test.py
+++ b/tests/gold_tests/tls/tls_tunnel.test.py
@@ -21,7 +21,7 @@
 '''
 
 # Define default ATS
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True)
 server_foo = Test.MakeOriginServer("server_foo", ssl=True)
 server_bar = Test.MakeOriginServer("server_bar", ssl=True)
 server2 = Test.MakeOriginServer("server2")
diff --git a/tests/gold_tests/tls/tls_verify_base.test.py b/tests/gold_tests/tls/tls_verify_base.test.py
index d43415e..1004e22 100644
--- a/tests/gold_tests/tls/tls_verify_base.test.py
+++ b/tests/gold_tests/tls/tls_verify_base.test.py
@@ -76,7 +76,8 @@
     'proxy.config.ssl.client.CA.cert.filename': 'signer.pem',
     'proxy.config.url_remap.pristine_host_hdr': 1,
     'proxy.config.exec_thread.autoconfig.scale': 1.0,
-    'proxy.config.ssl.client.sni_policy': 'host'
+    'proxy.config.ssl.client.sni_policy': 'host',
+    'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE'
 })
 
 ts.Disk.sni_yaml.AddLines([
diff --git a/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py b/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py
index 44391cb..11b6541 100644
--- a/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py
+++ b/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py
@@ -24,7 +24,7 @@
 Test.Setup.Copy("wait_reload.sh")
 
 # Define ATS and configure
-ts = Test.MakeATSProcess("ts", command="traffic_manager", enable_cache=False)
+ts = Test.MakeATSProcess("ts", enable_cache=False)
 nameserver = Test.MakeDNServer("dns", default='127.0.0.1')
 
 ts.Disk.File(ts.Variables.CONFIGDIR + "/test.inc", id="test_cfg", typename="ats:config")
@@ -63,7 +63,7 @@
 
 tr = Test.AddTestRun("Reload config")
 tr.StillRunningAfter = ts
-tr.Processes.Default.Command = 'traffic_ctl config reload'
+tr.Processes.Default.Command = f'traffic_ctl config reload'
 # Need to copy over the environment so traffic_ctl knows where to find the unix domain socket
 tr.Processes.Default.Env = ts.Env
 tr.Processes.Default.ReturnCode = 0
@@ -79,7 +79,3 @@
 )
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
-
-ts.Disk.manager_log.Content += Testers.ExcludesExpression(
-    "needs restart",
-    "Ensure that extra msg reported in issue #7530 does not reappear")
diff --git a/tests/tools/plugins/cont_schedule.cc b/tests/tools/plugins/cont_schedule.cc
index 4b8aa31..7706d89 100644
--- a/tests/tools/plugins/cont_schedule.cc
+++ b/tests/tools/plugins/cont_schedule.cc
@@ -42,8 +42,6 @@
 static TSCont contp_1 = nullptr;
 static TSCont contp_2 = nullptr;
 
-static int TSContSchedule_handler_1(TSCont contp, TSEvent event, void *edata);
-static int TSContSchedule_handler_2(TSCont contp, TSEvent event, void *edata);
 static int TSContScheduleOnPool_handler_1(TSCont contp, TSEvent event, void *edata);
 static int TSContScheduleOnPool_handler_2(TSCont contp, TSEvent event, void *edata);
 static int TSContScheduleOnThread_handler_1(TSCont contp, TSEvent event, void *edata);
@@ -51,71 +49,6 @@
 static int TSContThreadAffinity_handler(TSCont contp, TSEvent event, void *edata);
 
 static int
-TSContSchedule_handler_1(TSCont contp, TSEvent event, void *edata)
-{
-  TSDebug(DEBUG_TAG_HDL, "TSContSchedule handler 1 thread [%p]", TSThreadSelf());
-  if (thread_1 == nullptr) {
-    // First time entering this handler, before everything else starts.
-    thread_1 = TSEventThreadSelf();
-
-    // Set the affinity of contp_2 to thread_1, and schedule it twice.
-    // Since it's on the same thread, we don't need a delay.
-    TSDebug(DEBUG_TAG_HDL, "[%s] scheduling continuation", plugin_name);
-    TSContThreadAffinitySet(contp_2, thread_1);
-    TSContSchedule(contp_2, 0);
-    TSContSchedule(contp_2, 0);
-  } else if (thread_2 == nullptr) {
-    TSDebug(DEBUG_TAG_CHK, "fail [schedule delay not applied]");
-  } else {
-    // Second time in here, should be after the two scheduled handler_2 runs.
-    // Since handler_1 has no affinity set, we should be on a different thread now.
-    // Also, thread_2 should be the same as thread_1, since thread_1 was set as
-    // affinity for handler_2.
-    if (thread_2 != TSEventThreadSelf() && thread_2 == thread_1) {
-      TSDebug(DEBUG_TAG_CHK, "pass [should not be the same thread]");
-    } else {
-      TSDebug(DEBUG_TAG_CHK, "fail [on the same thread]");
-    }
-  }
-  return 0;
-}
-
-static int
-TSContSchedule_handler_2(TSCont contp, TSEvent event, void *edata)
-{
-  TSDebug(DEBUG_TAG_HDL, "TSContSchedule handler 2 thread [%p]", TSThreadSelf());
-  if (thread_2 == nullptr) {
-    // First time in this handler, should get here after handler_1,
-    // and also record the thread id.
-    thread_2 = TSEventThreadSelf();
-  } else if (thread_2 == TSEventThreadSelf()) {
-    // Second time in here, since the affinity is set to thread_1, we should be
-    // on the same thread as last time.
-    TSDebug(DEBUG_TAG_CHK, "pass [should be the same thread]");
-  } else {
-    TSDebug(DEBUG_TAG_CHK, "fail [not the same thread]");
-  }
-  return 0;
-}
-
-void
-TSContSchedule_test()
-{
-  contp_1 = TSContCreate(TSContSchedule_handler_1, TSMutexCreate());
-  contp_2 = TSContCreate(TSContSchedule_handler_2, TSMutexCreate());
-
-  if (contp_1 == nullptr || contp_2 == nullptr) {
-    TSDebug(DEBUG_TAG_SCHD, "[%s] could not create continuation", plugin_name);
-    abort();
-  } else {
-    TSDebug(DEBUG_TAG_SCHD, "[%s] scheduling continuation", plugin_name);
-    TSContScheduleOnPool(contp_1, 0, TS_THREAD_POOL_NET);
-    TSContThreadAffinityClear(contp_1);
-    TSContScheduleOnPool(contp_1, 200, TS_THREAD_POOL_NET);
-  }
-}
-
-static int
 TSContScheduleOnPool_handler_1(TSCont contp, TSEvent event, void *edata)
 {
   // This runs on ET_NET threads.
@@ -180,7 +113,7 @@
 static int
 TSContScheduleOnThread_handler_1(TSCont contp, TSEvent event, void *edata)
 {
-  // Mostly same as TSContSchedule_handler_1, no need to set affinity
+  // Mostly same as TSContScheduleOnPool_handler_1, no need to set affinity
   // since we are scheduling directly on to a thread.
   TSDebug(DEBUG_TAG_HDL, "TSContScheduleOnThread handler 1 thread [%p]", TSThreadSelf());
   if (thread_1 == nullptr) {
@@ -280,15 +213,12 @@
   if (event == TS_EVENT_LIFECYCLE_TASK_THREADS_READY) {
     switch (test_flag) {
     case 1:
-      TSContSchedule_test();
-      break;
-    case 2:
       TSContScheduleOnPool_test();
       break;
-    case 3:
+    case 2:
       TSContScheduleOnThread_test();
       break;
-    case 4:
+    case 3:
       TSContThreadAffinity_test();
       break;
     default:
@@ -301,20 +231,17 @@
 void
 TSPluginInit(int argc, const char *argv[])
 {
-  if (argc == 1) {
-    TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContSchedule");
-    test_flag = 1;
-  } else if (argc == 2) {
+  if (argc == 2) {
     int len = strlen(argv[1]);
     if (len == 4 && strncmp(argv[1], "pool", 4) == 0) {
       TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContScheduleOnPool");
-      test_flag = 2;
+      test_flag = 1;
     } else if (len == 6 && strncmp(argv[1], "thread", 6) == 0) {
       TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContScheduleOnThread");
-      test_flag = 3;
+      test_flag = 2;
     } else if (len == 8 && strncmp(argv[1], "affinity", 8) == 0) {
       TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContThreadAffinity");
-      test_flag = 4;
+      test_flag = 3;
     } else {
       goto Lerror;
     }
diff --git a/tests/tools/plugins/user_args.cc b/tests/tools/plugins/user_args.cc
index 71f1956..ca41430 100644
--- a/tests/tools/plugins/user_args.cc
+++ b/tests/tools/plugins/user_args.cc
@@ -26,8 +26,6 @@
 #include <cstring>
 #include <cstdio>
 
-#define NEW_APIS 1
-
 typedef struct {
   int TXN, SSN, VCONN, GLB;
   TSCont contp;
@@ -65,15 +63,9 @@
   TSHttpSsn ssnp = TSHttpTxnSsnGet(txnp);
   TSVConn vconnp = TSHttpSsnClientVConnGet(ssnp);
 
-#if NEW_APIS
   TSUserArgSet(txnp, gIX.TXN, (void *)"Transaction Data");
   TSUserArgSet(ssnp, gIX.SSN, (void *)"Session Data");
   TSUserArgSet(vconnp, gIX.VCONN, (void *)"VConn Data");
-#else
-  TSHttpTxnArgSet(txnp, gIX.TXN, (void *)"Transaction Data");
-  TSHttpSsnArgSet(ssnp, gIX.SSN, (void *)"Session Data");
-  TSVConnArgSet(vconnp, gIX.VCONN, (void *)"VConn Data");
-#endif
 
   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
 
@@ -91,16 +83,10 @@
   TSVConn vconnp = TSHttpSsnClientVConnGet(ssnp);
 
   if (TS_SUCCESS == TSHttpTxnClientRespGet(txnp, &bufp, &hdrs)) {
-#if NEW_APIS
     set_header(bufp, hdrs, "X-Arg-GLB", static_cast<const char *>(TSUserArgGet(nullptr, ix->GLB)));
     set_header(bufp, hdrs, "X-Arg-TXN", static_cast<const char *>(TSUserArgGet(txnp, ix->TXN)));
     set_header(bufp, hdrs, "X-Arg-SSN", static_cast<const char *>(TSUserArgGet(ssnp, ix->SSN)));
     set_header(bufp, hdrs, "X-Arg-VCONN", static_cast<const char *>(TSUserArgGet(vconnp, ix->VCONN)));
-#else
-    set_header(bufp, hdrs, "X-Arg-TXN", static_cast<const char *>(TSHttpTxnArgGet(txnp, ix->TXN)));
-    set_header(bufp, hdrs, "X-Arg-SSN", static_cast<const char *>(TSHttpSsnArgGet(ssnp, ix->SSN)));
-    set_header(bufp, hdrs, "X-Arg-VCONN", static_cast<const char *>(TSVConnArgGet(vconnp, ix->VCONN)));
-#endif
   }
 
   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
@@ -120,7 +106,6 @@
     TSError("[%s] Plugin registration failed", PLUGIN_NAME);
   }
 
-#if NEW_APIS
   if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_TXN, PLUGIN_NAME, "User args tests TXN", &gIX.TXN)) {
     TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve TXN arg.", PLUGIN_NAME);
     return;
@@ -134,18 +119,6 @@
     TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve GLB arg.", PLUGIN_NAME);
     return;
   }
-#else
-  if (TS_SUCCESS != TSHttpTxnArgIndexReserve(PLUGIN_NAME, "User args tests TXN", &gIX.TXN)) {
-    TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve TXN arg.", PLUGIN_NAME);
-    return;
-  } else if (TS_SUCCESS != TSHttpSsnArgIndexReserve(PLUGIN_NAME, "User args tests SSN", &gIX.SSN)) {
-    TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve SSN arg.", PLUGIN_NAME);
-    return;
-  } else if (TS_SUCCESS != TSVConnArgIndexReserve(PLUGIN_NAME, "User args tests VCONN", &gIX.VCONN)) {
-    TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve VCONN arg.", PLUGIN_NAME);
-    return;
-  }
-#endif
 
   // Setup the global slot value
   TSUserArgSet(nullptr, gIX.GLB, (void *)"Global Data");
@@ -184,7 +157,6 @@
   ix->contp = TSContCreate(cont_remap, nullptr);
   TSContDataSet(ix->contp, static_cast<void *>(ix));
 
-#if NEW_APIS
   if (TS_SUCCESS != TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, PLUGIN_NAME, &ix->TXN, nullptr)) {
     TSError("[%s] Failed to lookup TXN arg.", PLUGIN_NAME);
     return TS_ERROR;
@@ -198,18 +170,6 @@
     TSError("[%s] Failed to lookup GLB arg.", PLUGIN_NAME);
     return TS_ERROR;
   }
-#else
-  if (TS_SUCCESS != TSHttpTxnArgIndexNameLookup(PLUGIN_NAME, &ix->TXN, nullptr)) {
-    TSError("[%s] Failed to lookup TXN arg.", PLUGIN_NAME);
-    return TS_ERROR;
-  } else if (TS_SUCCESS != TSHttpSsnArgIndexNameLookup(PLUGIN_NAME, &ix->SSN, nullptr)) {
-    TSError("[%s] Failed to lookup SSN arg.", PLUGIN_NAME);
-    return TS_ERROR;
-  } else if (TS_SUCCESS != TSVConnArgIndexNameLookup(PLUGIN_NAME, &ix->VCONN, nullptr)) {
-    TSError("[%s] Failed to lookup VCONN arg.", PLUGIN_NAME);
-    return TS_ERROR;
-  }
-#endif
 
   *ih = static_cast<void *>(ix);
   return TS_SUCCESS;
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 135bed0..bb6a1f4 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -26,7 +26,7 @@
 	$(iocore_include_dirs) \
 	-I$(abs_top_srcdir)/include \
 	-I$(abs_top_srcdir)/include/wccp \
-	$(TS_INCLUDES)
+	$(TS_INCLUDES) @SWOC_INCLUDES@
 
 if BUILD_TEST_TOOLS
 bin_PROGRAMS = jtest/jtest
diff --git a/tools/build_h3_tools.sh b/tools/build_h3_tools.sh
index c147251..850c1cd 100755
--- a/tools/build_h3_tools.sh
+++ b/tools/build_h3_tools.sh
@@ -61,13 +61,27 @@
 
 set -x
 
+# Build quiche
+# Steps borrowed from: https://github.com/apache/trafficserver-ci/blob/main/docker/rockylinux8/Dockerfile
+echo "Building quiche"
+[ ! -d quiche ] && git clone --recursive https://github.com/cloudflare/quiche.git
+cd quiche
+cargo build -j4 --package quiche --release --features ffi,pkg-config-meta,qlog
+sudo mkdir -p /opt/quiche/lib/pkgconfig
+sudo mkdir -p /opt/quiche/include
+sudo cp target/release/libquiche.a /opt/quiche/lib/
+sudo cp target/release/libquiche.so /opt/quiche/lib/
+sudo cp quiche/include/quiche.h /opt/quiche/include/
+sudo cp target/release/quiche.pc /opt/quiche/lib/pkgconfig
+cd ..
+
 # OpenSSL needs special hackery ... Only grabbing the branch we need here... Bryan has shit for network.
 echo "Building OpenSSL with QUIC support"
 [ ! -d openssl-quic ] && git clone -b ${OPENSSL_BRANCH} --depth 1 https://github.com/quictls/openssl.git openssl-quic
 cd openssl-quic
 ./config enable-tls1_3 --prefix=${OPENSSL_PREFIX}
 ${MAKE} -j $(nproc)
-sudo ${MAKE} install
+sudo ${MAKE} -j install
 
 # The symlink target provides a more convenient path for the user while also
 # providing, in the symlink source, the precise branch of the OpenSSL build.
@@ -76,7 +90,12 @@
 
 # Then nghttp3
 echo "Building nghttp3..."
-[ ! -d nghttp3 ] && git clone https://github.com/ngtcp2/nghttp3.git
+if [ ! -d nghttp3 ]; then
+  git clone https://github.com/ngtcp2/nghttp3.git
+  cd nghttp3
+  git checkout -b v0.8.0 v0.8.0
+  cd ..
+fi
 cd nghttp3
 autoreconf -if
 ./configure \
@@ -92,7 +111,12 @@
 
 # Now ngtcp2
 echo "Building ngtcp2..."
-[ ! -d ngtcp2 ] && git clone https://github.com/ngtcp2/ngtcp2.git
+if [ ! -d ngtcp2 ]; then
+  git clone https://github.com/ngtcp2/ngtcp2.git
+  cd ngtcp2
+  git checkout -b v0.12.0 v0.12.0
+  cd ..
+fi
 cd ngtcp2
 autoreconf -if
 ./configure \
@@ -108,9 +132,13 @@
 
 # Then nghttp2, with support for H3
 echo "Building nghttp2 ..."
-[ ! -d nghttp2 ] && git clone https://github.com/tatsuhiro-t/nghttp2.git
+if [ ! -d nghttp2 ]; then
+  git clone https://github.com/tatsuhiro-t/nghttp2.git
+  cd nghttp2
+  git checkout -b v1.51.0 v1.51.0
+  cd ..
+fi
 cd nghttp2
-git checkout --track -b quic origin/quic
 autoreconf -if
 ./configure \
   --prefix=${BASE} \
@@ -118,7 +146,8 @@
   CFLAGS="${CFLAGS}" \
   CXXFLAGS="${CXXFLAGS}" \
   LDFLAGS="${LDFLAGS}" \
-  --enable-lib-only
+  --enable-http3 \
+  --enable-app
 ${MAKE} -j $(nproc)
 sudo ${MAKE} install
 cd ..